Skip to content

S3 Bucket ACL AccessControlListNotSupported Error in Terraform

Problem Statement

When creating aws_s3_bucket_acl resources in Terraform after April 2023, you may encounter this error:
"Error: error creating S3 bucket ACL for bucket-name: AccessControlListNotSupported: The bucket does not allow ACLs │ status code: 400"

This occurs due to AWS security changes implemented in April 2023 that disable Access Control Lists (ACLs) by default for new S3 buckets. AWS now recommends bucket policies instead of ACLs for access management.

Terraform's default configuration conflict with this change because:

  1. aws_s3_bucket_acl resources implicitly require ACLs to be enabled
  2. New buckets default to ObjectOwnership = "BucketOwnerEnforced" (which blocks ACLs)
  3. Terraform creates ACL resources before ownership controls by default

Solution 1: Enable ACLs with Ownership Controls (Quick Fix)

Add ownership controls to enable ACL support before creating the ACL resource:

terraform
# Ownership controls MUST be created first
resource "aws_s3_bucket_ownership_controls" "example" {
  bucket = aws_s3_bucket.my_bucket.id
  rule {
    # Required for ACL support
    object_ownership = "BucketOwnerPreferred" 
  }
}

resource "aws_s3_bucket_acl" "example" {
  bucket = aws_s3_bucket.my_bucket.id
  acl    = "private"

  # Critical dependency
  depends_on = [aws_s3_bucket_ownership_controls.example]
}

Key Notes:

  • Use ObjectWriter or BucketOwnerPreferred ownership types
  • depends_on is mandatory to enforce creation order
  • Works for existing Terraform configurations with minimal changes

Solution 2: Using the Official S3 Bucket Module (Best Practice)

For new implementations, use HashiCorp's maintained S3 module with built-in ACL handling:

terraform
module "s3_bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "~> 3.0"

  bucket = "my-bucket"
  
  # These enable ACL support automatically
  control_object_ownership = true
  object_ownership         = "BucketOwnerPreferred"
  
  # Standard ACL declaration
  acl = "private"
}

Advantages of this approach:

  • Automatically handles creation order dependencies
  • Implements AWS best practices
  • Provides additional security features
  • Maintained by HashiCorp/AWS experts

Where possible, replace ACLs with bucket policies (AWS preferred method):

terraform
resource "aws_s3_bucket_policy" "example" {
  bucket = aws_s3_bucket.my_bucket.id
  policy = jsonencode({
    Version : "2012-10-17",
    Statement : [
      {
        Effect : "Allow",
        Principal : "*",
        Action : "s3:GetObject",
        Resource : "arn:aws:s3:::my-bucket/*"
      }
    ]
  })
}

Why This Error Occurs

AWS made two significant changes:

  1. New buckets default to ACL-disabled mode (BucketOwnerEnforced)
  2. ACLs are being phased out as a legacy authorization method

Terraform's resource creation order exacerbates the problem:

Best Practices Summary

  1. For existing Terraform configurations
    Use Solution 1 with explicit ownership controls and dependencies

  2. For new infrastructure
    Use Solution 2 with the official S3 bucket module

  3. Long-term strategy
    Migrate to bucket policies (Solution 3) per AWS security recommendations

AWS ACL Deprecation Note

Bucket ACLs are being phased out - avoid them for new development. Use bucket policies or S3 Access Points instead.

Troubleshooting Tips

  • Always apply ownership controls before ACL resources
  • Verify ownership settings in AWS Console:
    S3 → Bucket → Permissions → Object Ownership
  • Enable access logs to diagnose permission issues
  • Use terraform apply -replace to force resource recreation if stuck in bad state

Verifying Configuration

Check your ownership settings with AWS CLI:

bash
aws s3api get-bucket-ownership-controls \
  --bucket YOUR_BUCKET_NAME \
  --query "OwnershipControls.Rules[0].ObjectOwnership"

Should return BucketOwnerPreferred or ObjectWriter