Accesscontrollistnotsupported The Bucket Does Not Allow ACLs Terraform | Bucket Rule Fix

AccessControlListNotSupported in Terraform means your S3 bucket has ACLs disabled, so ACL changes fail until you switch to policies or reenable ACLs.

Hitting the error string AccessControlListNotSupported: The bucket does not allow ACLs in Terraform feels confusing at first, because the bucket may look perfectly fine in the console. This message comes from Amazon S3 and tells you that the bucket now ignores Access Control Lists, while your Terraform code still tries to set one. The good news is that this is a configuration mismatch, not a broken bucket.

This article walks through what the error means, why it started appearing more often after recent S3 changes, and how to fix Accesscontrollistnotsupported The Bucket Does Not Allow ACLs Terraform cleanly. You will see both quick Terraform edits and console checks so you can decide whether to keep ACLs or move fully to bucket policies.

What Accesscontrollistnotsupported Means In Terraform

When Terraform talks to S3 and tries to set an ACL on a bucket or object, S3 checks whether that bucket still accepts ACLs. If the bucket uses the Bucket owner enforced object ownership mode, S3 turns ACLs off. Any attempt to write a new ACL then fails with the AccessControlListNotSupported error and the message that the bucket does not allow ACLs.

From Terraform’s point of view, this shows up in apply output similar to:

Error: error creating S3 bucket ACL for my-bucket:
AccessControlListNotSupported: The bucket does not allow ACLs
status code: 400

To understand what is going on, it helps to separate three pieces: the bucket’s object ownership mode, ACL settings in your Terraform code, and the AWS provider resources you use.

  • Object ownership mode — Controls who owns uploaded objects and whether ACLs are active for the bucket.
  • ACL usage in Terraform — Any acl = "public-read" style field or use of aws_s3_bucket_acl tries to set an ACL.
  • Provider resources — Resources such as aws_s3_bucket_ownership_controls and aws_s3_bucket_public_access_block change how ACLs behave.

Once object ownership switches to bucket owner enforced, ACL writes stop working. Terraform keeps sending ACL updates until you remove the ACL configuration or change the bucket’s ownership mode back to one that allows ACLs.

Why New S3 Buckets Often Block ACLs Now

In recent years, AWS has pushed S3 users toward bucket policies and away from ACLs. New general purpose S3 buckets now commonly use the bucket owner enforced mode by default. In that mode, ACLs no longer control access. The bucket owner owns every object, and fine-grained access rules move into policies.

Terraform projects that predate these defaults often still rely on ACL fields or on the aws_s3_bucket_acl resource. When you apply those configurations against a newer bucket with ACLs disabled, Terraform runs into the AccessControlListNotSupported error.

The table below sums up how object ownership and ACLs line up and what that means for Terraform plans that touch ACLs.

Object Ownership Setting ACL Behavior Terraform Effect
Bucket owner enforced ACLs disabled for writes Any ACL field or aws_s3_bucket_acl fails
Bucket owner preferred ACLs allowed, owner wins on conflict ACL changes work, but policies still recommended
Object writer ACLs allowed, object writer owns objects ACL changes work, often used with legacy setups

If you import an older bucket, Terraform may read a different ownership mode than the one you assume. A quick check in the S3 console on the bucket’s Object Ownership tab usually explains why the error appears.

Accesscontrollistnotsupported The Bucket Does Not Allow ACLs Terraform Error Fix

Fixing Accesscontrollistnotsupported The Bucket Does Not Allow ACLs Terraform comes down to a choice. You can either move away from ACLs and use policies, or you can switch the bucket back to a mode that accepts ACLs. Most teams now choose the first path, because it lines up with current S3 access patterns.

Step 1 Check Which Resource Sets The ACL

Start by finding where Terraform sets an ACL. Search your configuration for any acl = field or for the aws_s3_bucket_acl resource.

  • Search for ACL fields — Look for acl = "private", "public-read", or similar inside aws_s3_bucket resources.
  • Search for bucket ACL resources — Check whether your code defines resource "aws_s3_bucket_acl" for the same bucket.
  • Check modules — If you use community modules, read their inputs to see whether they set an ACL behind the scenes.

A common pattern that now fails looks like this:

resource "aws_s3_bucket" "logs" {
  bucket = "my-logs-bucket"
  acl    = "private"
}

On a bucket with bucket owner enforced, that acl line causes the error.

Step 2 Remove ACL Usage For New Buckets

For buckets that do not need public read access, the simplest fix is to stop using ACLs at all. Let ownership controls and policies handle access.

  • Drop the acl field — Remove acl = "private" or similar from aws_s3_bucket resources that target buckets with ACLs disabled.
  • Replace aws_s3_bucket_acl — Delete aws_s3_bucket_acl resources and move their intent into a bucket policy.
  • Align modules with this pattern — Pass inputs to modules so they do not set ACLs, or upgrade to a version that follows the newer S3 model.

After this change, Terraform stops trying to write ACLs. The bucket still blocks ACLs, but there are no ACL updates in the plan, so the error vanishes.

Step 3 Add A Bucket Policy For Access Control

Once ACL lines disappear, you still need a way to grant access. Bucket policies fill that role. They let you express who can read or write objects in one JSON document attached to the bucket.

  • Create a policy document — Use aws_iam_policy_document to describe allowed actions, resources, and principals.
  • Attach it with aws_s3_bucket_policy — Point the policy resource at the same bucket Terraform manages.
  • Grant access by role or user — Reference IAM roles used by your apps and pipelines instead of public ACL flags.

A basic Terraform pattern looks like this:

data "aws_iam_policy_document" "logs_bucket" {
  statement {
    sid    = "AllowAppRead"
    effect = "Allow"

    principals {
      type        = "AWS"
      identifiers = [aws_iam_role.app.arn]
    }

    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.logs.arn}/*"]
  }
}

resource "aws_s3_bucket_policy" "logs" {
  bucket = aws_s3_bucket.logs.id
  policy = data.aws_iam_policy_document.logs_bucket.json
}

This pattern keeps S3 ownership simple and gives you a single place to reason about access rules.

Step 4 Switch Back To ACLs Only When You Must

Some legacy flows still depend on ACLs, such as cross-account object writes where the writer sets grants. In that case you need the bucket to allow ACLs again.

  • Change object ownership mode — In the S3 console, turn off bucket owner enforced and pick bucket owner preferred or object writer for the bucket.
  • Match Terraform ownership controls — If you use aws_s3_bucket_ownership_controls, set its object_ownership field to the same mode you selected in the console.
  • Reapply Terraform — Run terraform apply and confirm that ACL-related resources now plan and apply cleanly.

For long-term maintenance, limiting ACL usage to narrow, well-understood cases keeps your setup easier to reason about.

Terraform Patterns That Trigger The Bucket Does Not Allow ACLs

Once you see the error a few times, certain Terraform patterns stand out as repeat offenders. Cleaning these up across your codebase prevents surprises on the next deploy.

Mixing Ownership Controls And ACL Resources

One common pattern defines both ownership controls and a bucket ACL for the same bucket. When ownership controls move to bucket owner enforced, the bucket ACL resource becomes incompatible.

resource "aws_s3_bucket" "assets" {
  bucket = "my-assets-bucket"
}

resource "aws_s3_bucket_ownership_controls" "assets" {
  bucket = aws_s3_bucket.assets.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_acl" "assets_acl" {
  bucket = aws_s3_bucket.assets.id
  acl    = "public-read"
}

Here, the ownership controls resource tells S3 to ignore ACLs. The ACL resource still tries to set one, which leads straight to The bucket does not allow ACLs.

  • Keep ownership and ACLs consistent — If you enable bucket owner enforced, drop ACL resources for that bucket.
  • Use policies for public read — Replace public read ACLs with a tight bucket policy that grants read access only where needed.
  • Audit shared modules — Check shared Terraform modules for this pattern so you do not reintroduce it later.

Using Legacy Public Buckets With New Defaults

Another pattern appears when you recreate a bucket name that once used ACLs, but now lives in a region where new buckets block ACLs. Old tutorials often show public website buckets using ACLs, while current defaults push you toward policies and the static website hosting feature or CloudFront.

  • Stop relying on public-read — Treat acl = "public-read" as a red flag in new infrastructure.
  • Turn on access block settings — Use aws_s3_bucket_public_access_block to block public ACLs and rely on controlled policies instead.
  • Serve websites through CloudFront — Point a distribution at a private bucket and handle public access at the edge.

This keeps your buckets aligned with modern S3 defaults and removes a whole class of ACL-related errors from your workflow.

Safer Access Control For S3 Buckets In Terraform

Solving the immediate error is one step; reshaping access control so it stays predictable is the next. Terraform gives you good building blocks to express S3 access without ACLs, and they map neatly to AWS guidance.

Use IAM Roles And Policies As The Primary Layer

Instead of tying access to objects with ACLs, connect S3 actions to IAM roles and bucket policies. This keeps access rules close to the identities that use the bucket and avoids hidden side effects when someone uploads with a custom ACL flag.

  • Model readers and writers as roles — Give each app or pipeline its own IAM role and refer to that role inside bucket policies.
  • Grant least privilege — Limit actions in the policy document to the exact S3 operations each role needs.
  • Use condition keys — Narrow access with conditions such as prefixes, VPC endpoints, or TLS requirements.

Keep Bucket Policies Small And Focused

Bucket policies grow messy when every team adds statements by hand. Terraform helps by letting you compose a policy from several data sources and then render a single JSON document.

  • Split policy input by use case — Create separate aws_iam_policy_document data blocks for logs, assets, and uploads.
  • Merge them in one document — Use the source_json field or statements from multiple sources to join them.
  • Attach only one policy resource — Point a single aws_s3_bucket_policy at the bucket to avoid clashes.

This structure keeps your access model readable and reduces the chance that a future change reintroduces ACLs just to “make something work quickly.”

Troubleshooting Checklist For Persistent ACL Errors

If the error still appears after your first round of changes, a short checklist helps you track down the last place where Terraform and S3 disagree.

  • Confirm object ownership — In the S3 console, read the Object Ownership setting for the bucket and match it with any aws_s3_bucket_ownership_controls resource.
  • Search for acl fields again — Run a project-wide search for acl = and make sure no resource still sets it for that bucket.
  • Inspect module versions — Check module changelogs to see whether they changed how they handle ACLs or ownership controls.
  • Review the plan output carefully — Look for any resource of type aws_s3_bucket_acl or any diff that mentions ACL data.
  • Consider bucket recreation only as last resort — If a test bucket carries conflicting history and you can rename it, recreating it with a clean Terraform pattern may be easier than untangling every past change.

Once you align object ownership, remove ACL usage from Terraform, and shift access to policies and IAM roles, the The bucket does not allow ACLs error stops appearing. More importantly, your S3 access story becomes clearer for everyone who reads your code, not just the person who remembers the latest S3 defaults.