How to fix Error: error deleting S3 Bucket: BucketNotEmpty in Terraform

TerraformBEGINNERMEDIUMUpdated Dec 30, 2025ยท 122 views

This error occurs when Terraform tries to delete an S3 bucket that still contains objects. By default, Terraform prevents bucket deletion to protect against accidental data loss. The fix is to enable force_destroy in your aws_s3_bucket resource or manually delete objects first.

Quick fix

Fixes BucketNotEmpty

resource "aws_s3_bucket" "example" {
  bucket        = "my-terraform-bucket"
  force_destroy = true
}
  1. Enable force_destroy in your Terraform configuration
  2. Apply the configuration update before destroying
  3. Target the bucket for update if needed
  4. Manually empty the bucket via AWS Console (alternative)
terraform โ€” error
resource "aws_s3_bucket" "example" {
bucket = "my-terraform-bucket"
force_destroy = true
}
Error: error deleting S3 Bucket: BucketNotEmpty

What this error means

AWS S3 requires that buckets be completely empty before they can be deleted. When you run `terraform destroy` on an S3 bucket resource, Terraform calls the AWS API to delete the bucket. If the bucket contains any objects (files), AWS returns the "BucketNotEmpty" error. This is a safety feature - AWS doesn't allow deletion of non-empty buckets to prevent accidental data loss. Terraform honors this AWS requirement by default. The error typically occurs when: - You created an S3 bucket with Terraform, uploaded objects to it (either manually or through other infrastructure), then tried to destroy your Terraform stack - The bucket has versioning enabled and contains object versions or delete markers - The bucket was populated by application logs, backups, or other automated processes The solution depends on your use case: do you want to delete the objects along with the bucket, or preserve the bucket and its contents?

Typical symptoms

  • terraform destroy fails with 'Error: error deleting S3 Bucket: BucketNotEmpty'
  • The error message includes 'The bucket you tried to delete is not empty'
  • Terraform shows the S3 bucket resource still exists in the state file after failed destroy
  • You have uploaded objects to the bucket but terraform destroy cannot remove them
  • The destroy operation hangs or fails immediately after showing the bucket deletion step
  • This error only occurs on terraform destroy, not during terraform plan

Common causes

  1. The aws_s3_bucket resource has force_destroy = false (the default)
  2. Objects exist in the bucket from manual uploads or other processes
  3. The bucket has versioning enabled and contains multiple object versions
  4. Delete markers exist in the bucket from previous object deletions
  5. Application or services have written objects to the bucket that Terraform doesn't manage
  6. The bucket is used for server access logging or other AWS-managed content

How to fix "Error: error deleting S3 Bucket: BucketNotEmpty"

1Enable force_destroy in your Terraform configuration

The quickest fix is to set force_destroy = true on your aws_s3_bucket resource. This tells Terraform to delete all objects in the bucket before deleting the bucket itself.

hcl
resource "aws_s3_bucket" "example" {
  bucket        = "my-terraform-bucket"
  force_destroy = true
}

If you have a bucket without force_destroy already defined:

hcl
# Before (fails on destroy)
resource "aws_s3_bucket" "logs" {
  bucket = "my-app-logs"
}

# After (allows destroy)
resource "aws_s3_bucket" "logs" {
  bucket        = "my-app-logs"
  force_destroy = true
}

Then apply the change:

bash
terraform apply

Now terraform destroy will work, removing all objects and then the bucket.

Warning: This is destructive. Only use this if you're comfortable with the bucket and all its contents being permanently deleted.

2Apply the configuration update before destroying

After adding force_destroy = true, you must apply the change to the bucket before destroying:

bash
# Step 1: Apply the force_destroy change
terraform apply

# Step 2: Destroy the bucket (now with force_destroy enabled)
terraform destroy

You cannot skip the apply step. If you try to destroy immediately without applying the force_destroy change, the error will still occur.

If you're using terraform destroy with auto-approve:

bash
terraform apply -auto-approve
terraform destroy -auto-approve
3Target the bucket for update if needed

If you have a large Terraform configuration and want to update only the S3 bucket:

bash
# Apply changes only to the S3 bucket resource
terraform apply -target=aws_s3_bucket.example

# Then destroy
terraform destroy

Or if the bucket is in a module:

bash
terraform apply -target=module.storage.aws_s3_bucket.example
terraform destroy

This approach is useful when you don't want to apply other infrastructure changes while fixing the bucket destruction issue.

4Manually empty the bucket via AWS Console (alternative)

If you don't want to enable force_destroy in your code, you can manually delete the bucket contents first:

1. Go to the AWS S3 console: https://s3.console.aws.amazon.com/s3/
2. Find your bucket and click on it
3. Click "Select All" to select all objects
4. Click "Delete" and confirm

Or use the AWS CLI:

bash
# Remove all objects from the bucket
aws s3 rm s3://my-terraform-bucket --recursive

# If versioning is enabled, you may need to remove delete markers too
aws s3api list-object-versions --bucket my-terraform-bucket --output json \
  | jq -r '.DeleteMarkers[] | "\(.Key) \(.VersionId)"' \
  | while read key version_id; do
      aws s3api delete-object --bucket my-terraform-bucket --key "$key" --version-id "$version_id"
    done

# Now terraform destroy should work
terraform destroy

After the bucket is empty, terraform destroy will succeed without needing to modify your code.

Drawback: You have to do this manually each time, and it's error-prone in CI/CD pipelines.

5Handle versioned buckets with delete markers

If your bucket has versioning enabled, simply deleting current versions isn't enough. You must also remove delete markers:

bash
# List all versions and delete markers
aws s3api list-object-versions --bucket my-terraform-bucket --output table

# Remove all object versions
aws s3api list-object-versions --bucket my-terraform-bucket \
  --query 'Versions[].{Key:Key,VersionId:VersionId}' \
  --output text | while read key version_id; do
    aws s3api delete-object --bucket my-terraform-bucket --key "$key" --version-id "$version_id"
  done

# Remove all delete markers
aws s3api list-object-versions --bucket my-terraform-bucket \
  --query 'DeleteMarkers[].{Key:Key,VersionId:VersionId}' \
  --output text | while read key version_id; do
    aws s3api delete-object --bucket my-terraform-bucket --key "$key" --version-id "$version_id"
  done

If using force_destroy = true, Terraform handles this automatically.

6Prevent accidental loss by importing existing buckets

If you want to manage an existing S3 bucket with Terraform but keep it safe from accidental destruction:

hcl
# Define the resource WITHOUT force_destroy
resource "aws_s3_bucket" "existing" {
  bucket        = "my-existing-bucket"
  force_destroy = false  # Explicit - prevents accidents
}

Import the existing bucket:

bash
terraform import aws_s3_bucket.existing my-existing-bucket

Now terraform destroy will fail safely with BucketNotEmpty, protecting the bucket from accidental deletion. This is useful for:
- Shared infrastructure
- Buckets with important data
- Buckets shared across multiple Terraform projects

Advanced notes

### Force Destroy Behavior in Terraform AWS Provider

When you set force_destroy = true, Terraform performs this sequence:
1. Lists all objects in the bucket
2. Deletes every object version (if versioning is enabled)
3. Deletes every delete marker
4. Finally deletes the bucket

This is recursive and handles nested prefixes correctly.

### Versioning Gotchas

If a bucket has versioning enabled and you delete objects without deleting versions:
- Current object versions are deleted (versioning = SUSPENDED)
- But all previous versions and delete markers remain
- The bucket is still not empty

Terraform's force_destroy removes all versions and delete markers, so it handles this correctly. The AWS Console's "Empty" button also handles this.

### S3 Bucket Policies and Permissions

Ensure your IAM user/role has the required permissions:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:DeleteObject",
        "s3:DeleteObjectVersion",
        "s3:ListBucket",
        "s3:ListBucketVersions",
        "s3:DeleteBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-terraform-bucket",
        "arn:aws:s3:::my-terraform-bucket/*"
      ]
    }
  ]
}

### Using force_destroy in Production

For production workloads, consider:

1. Separate state files: Use different Terraform workspaces or modules for persistent vs. ephemeral resources
2. Prevent destroy: Use Terraform's prevent_destroy = true to protect critical buckets:

hcl
resource "aws_s3_bucket" "production_data" {
  bucket        = "prod-data"
  force_destroy = false

  lifecycle {
    prevent_destroy = true
  }
}

3. Require approval: Use terraform destroy with manual review rather than automated destruction
4. Backup strategy: Always maintain backups outside the S3 bucket

### Bucket Ownership Control

AWS introduced Bucket Ownership Controls. If your bucket has strict ownership restrictions, you may need to adjust those before destruction:

hcl
resource "aws_s3_bucket_ownership_controls" "example" {
  bucket = aws_s3_bucket.example.id

  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}

### S3 Access Logging Buckets

If your bucket is used as a target for S3 access logging:

hcl
# Logging bucket - safe to destroy
resource "aws_s3_bucket" "logs" {
  bucket        = "my-logs-bucket"
  force_destroy = true
}

# Source bucket - references the logging bucket
resource "aws_s3_bucket" "data" {
  bucket = "my-data-bucket"
}

resource "aws_s3_bucket_logging" "data" {
  bucket = aws_s3_bucket.data.id

  target_bucket = aws_s3_bucket.logs.id
  target_prefix = "log/"
}

When destroying, Terraform will first remove the logging configuration from the source bucket, then destroy the logging bucket. Order matters in your destroy operation.

Frequently asked questions

What does "BucketNotEmpty" mean?
AWS S3 requires that buckets be completely empty before they can be deleted. When you run terraform destroy on an S3 bucket resource, Terraform calls the AWS API to delete the bucket. If the bucket contains any objects (files), AWS returns the "BucketNotEmpty" error.
How do I fix "BucketNotEmpty"?
This error occurs when Terraform tries to delete an S3 bucket that still contains objects. By default, Terraform prevents bucket deletion to protect against accidental data loss. The fix is to enable force_destroy in your aws_s3_bucket resource or manually delete objects first.
What causes "BucketNotEmpty"?
The aws_s3_bucket resource has force_destroy = false (the default) Objects exist in the bucket from manual uploads or other processes The bucket has versioning enabled and contains multiple object versions
Is the "BucketNotEmpty" error serious?
It is a medium-severity error โ€” more of a blocker than a risk. The steps above resolve it.

Official resources & further reading