S3 "Bucket Does Not Allow ACLs": Fix the Upload Error
If aws s3api put-object --acl public-read is failing with AccessControlListNotSupported: The bucket does not allow ACLs, the fastest fix is almost never "turn ACLs back on." It's to drop the --acl flag entirely and grant access with a bucket policy instead — because since April 2023, every new S3 bucket ships with ACLs disabled by default, and re-enabling them is usually solving the wrong problem.
Why you're seeing this now
This is not a bug in your script and it's not an AWS outage. As of April 27, 2023, Amazon S3 automatically enables the Block Public Access settings and disables access control lists (ACLs) for every new bucket you create, in every region. If your bucket was created after that date and you never touched its Object Ownership setting, it landed with ACLs off by default — which is exactly what's rejecting your --acl public-read request.
The underlying feature is called S3 Object Ownership, and it controls two things at once: who owns objects that other accounts upload into your bucket, and whether ACLs are allowed at all. It has three possible settings.
🙋♂️ Jake's Reality Check
"I didn't set any 'Object Ownership' anything. I just made a bucket and ran the same upload command I've used for two years. Why is it suddenly broken?"
You didn't have to touch it — AWS changed the default under you. Any bucket you create today gets the strict setting automatically. Buckets you created years ago, before the change, kept whatever setting they had. That's why the same command still works on your old bucket and fails on the new one.
Here's what the three settings actually mean, worded the way you'll see them in the console:
- Bucket owner enforced (the current default): ACLs are disabled entirely. The bucket owner automatically owns and has full control over every object, no matter who uploaded it. Access is controlled only through bucket policies and IAM policies. This is the setting throwing your error.
- Bucket owner preferred: ACLs are allowed. New objects written with the
bucket-owner-full-controlcanned ACL become owned by the bucket owner; anything else keeps the uploader's ownership. - Object writer: ACLs are allowed and the account that uploads an object owns it and controls its permissions — this was the old default behavior, going back to S3's earliest days.
✅ Why this is the one to use
Ethan puts it bluntly: "Enforced is the boring choice. Boring is what you want on a bucket."
AWS's own documentation backs him up — a majority of modern use cases don't need ACLs, and this setting removes an entire category of misconfiguration: an ACL accidentally left open to "Everyone." Don't fight the error by re-enabling ACLs unless you have a specific, documented reason you can name.
Fix 1: stop sending an ACL at all
If you're using --acl public-read, --acl private, or any canned ACL out of habit from an older tutorial, the cleanest fix is to remove the flag completely. A PutObject request that doesn't specify an ACL succeeds on a bucket with ACLs disabled, because there's nothing for S3 to reject.
- Find every upload command that includes
--acl. Check CLI scripts, CI/CD pipelines, Lambda functions using an SDK, and any Terraform or CloudFormation resource that sets anaclattribute. - Remove the ACL argument.
aws s3api put-object --bucket amzn-s3-demo-bucket --key key-name --body path-to-file— no--aclat all — succeeds on an ACL-disabled bucket. - Re-run the upload. If it still fails with a different error (commonly
AccessDenied), that's a permissions problem, not an ACL problem — check the IAM identity or bucket policy next, not the ACL.
This is the right move if your actual goal was never "make this specific object public" — it was just "upload the file," and the ACL was leftover boilerplate. It's also the only fix that requires touching zero AWS settings: nothing about the bucket changes, you just stop asking it to do something it no longer does.
Fix 2: use bucket-owner-full-control, not public-read
There's exactly one ACL value that a Bucket owner enforced bucket will still accept: bucket-owner-full-control. AWS's own troubleshooting documentation confirms both of these succeed on a bucket with ACLs disabled — a PutObject with the bucket-owner-full-control canned ACL, and a PutObject with no ACL specified at all. Every other canned ACL, including public-read, private, and authenticated-read, is rejected.
🙋♂️ Jake's Reality Check
"Okay but why does that one ACL still work when every other one is blocked? Isn't that inconsistent?"
It's not really an ACL in the way you're thinking of one. bucket-owner-full-control doesn't grant anyone new access — it just confirms the bucket owner ends up owning the object, which is already true by default under this setting. AWS lets it through because it's a no-op, not a permission grant. Anything that would actually change who can read or write the object gets rejected.
When this matters in practice: cross-account uploads, where one AWS account is writing objects into a bucket owned by a different account (a common pattern for centralized logging or a shared data lake). Historically that other account had to include bucket-owner-full-control or the bucket owner couldn't manage its own objects. Under Bucket owner enforced, the bucket owner already has full control automatically — so this ACL value is now mostly there for backward compatibility with scripts that already include it.
Fix 3: change the Object Ownership setting (only if you truly need ACLs)
If you have a genuine legacy requirement — per-object ACLs for a workload you can't re-architect right now, or a third-party tool that hardcodes ACL calls — you can re-enable them by changing Object Ownership away from Bucket owner enforced. Console path: open the bucket, go to Permissions, find Object Ownership, and choose Edit. Pick Bucket owner preferred or Object writer depending on who should own newly uploaded objects.
From the CLI, the same change is one command:
aws s3api put-bucket-ownership-controls \
--bucket amzn-s3-demo-bucket \
--ownership-controls="Rules=[{ObjectOwnership=BucketOwnerPreferred}]"
Swap BucketOwnerPreferred for ObjectWriter if you want the uploading account to own its own objects, the way S3 behaved before 2023. This command produces no output when it succeeds — that's normal, not a sign it failed.
⚠️ What this actually breaks
Re-enabling ACLs doesn't grant anyone access by itself, but it reopens the door for someone, later, to attach an ACL that grants "Everyone" or "Authenticated Users" read or write access without going through a reviewable bucket policy. AWS's own guidance is blunt about it: don't grant write access to the Everyone or Authenticated Users groups through an ACL. If you turn ACLs back on, treat every ACL change on that bucket the way you'd treat a policy change — reviewed, not casual.
Which fix should you actually use?
| Method | Changes bucket settings? | Use it when |
|---|---|---|
Drop the --acl flag |
No | You never actually needed the ACL — it was leftover boilerplate from an old script or tutorial. |
Use --acl bucket-owner-full-control |
No | A cross-account upload where the bucket owner needs full control confirmed on the object. |
| Bucket policy (public or scoped read) | Policy only, ACLs untouched | You genuinely want a set of objects readable by the public or by specific accounts/roles. |
| Change Object Ownership to Bucket owner preferred / Object writer | Yes — re-enables ACLs | A legacy tool or workload you can't rewrite right now that specifically requires per-object ACLs. |
If your real goal was a public bucket, you don't need ACLs for that
This is the part almost every quick fix online skips: most people hitting this error weren't trying to preserve some elaborate ACL setup — they wanted a file to be publicly readable, and --acl public-read was the command they'd always used to get there. Under Bucket owner enforced, public read access comes from a bucket policy instead, and it needs two separate things to line up.
Step one: adjust Block Public Access
S3 Block Public Access has four independent settings, and since April 2023 all four are on by default for new buckets: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, and RestrictPublicBuckets. To grant public read access through a bucket policy, you specifically need to turn off BlockPublicPolicy and RestrictPublicBuckets — both at the bucket level and, if it's set there, at the account level. You can leave BlockPublicAcls and IgnorePublicAcls on; you're not using ACLs for this.
aws s3api put-public-access-block \
--bucket amzn-s3-demo-bucket \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=false,RestrictPublicBuckets=false
Step two: write the bucket policy
With Block Public Access loosened for policy-based access, attach a policy that grants s3:GetObject to everyone for the objects you want public:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}]
}
Save that as a file and apply it with aws s3api put-bucket-policy --bucket amzn-s3-demo-bucket --policy file://policy.json. Notice the resource is scoped to one bucket's objects, not your whole account — that scoping is the entire advantage a bucket policy has over an ACL: you can read exactly what's public in one file, instead of checking every object's ACL individually.
🕐 What changed between versions
- Before April 2023: new buckets defaulted to Object writer, ACLs enabled, and Block Public Access off —
--acl public-readworked immediately on any new bucket. - Since April 27, 2023: new buckets default to Bucket owner enforced (ACLs disabled) with all four Block Public Access settings on.
- What that means for you: nothing about your AWS account changed on that date — this only affects buckets created after it, unless you've since edited an older bucket's settings.
S3 ACL vs bucket policy: what each one actually controls
An ACL (access control list) is a small, per-object or per-bucket permission list — think of a sticky note taped to a single file that says who can read or write it. A bucket policy is a single JSON document attached to the whole bucket that can express far more nuanced rules: specific IAM users, specific prefixes, time-based conditions, IP restrictions, and more.
| Question | ACL | Bucket policy |
|---|---|---|
| Where it's attached | Individual object or bucket | The whole bucket, one document |
| Can scope by prefix or key pattern? | No | Yes |
| Works on a Bucket owner enforced bucket? | No — setting one fails with this error | Yes |
| Easy to audit everything at once? | No — check each object individually | Yes — one JSON document per bucket |
🙋♂️ Jake's Reality Check
"So a bucket policy just does what an ACL did, with extra steps?"
It does more, not the same thing with extra steps. Ethan puts it this way: "An ACL was a sticky note on one file. A bucket policy is the whole filing system, in one drawer you can actually open." An ACL can only say "this account, these permissions, this object." A bucket policy can say "any request for objects under this prefix gets read access" — reviewable in one place instead of scattered across every file.
IAM policies are the third piece, attached to a user or role rather than a bucket, and they're evaluated together with the bucket policy — a request only succeeds if nothing in either one denies it and something in one of them allows it.
Checking what your bucket is currently set to
Before changing anything, confirm what's actually configured. Two commands answer that:
- Check the Object Ownership setting:
aws s3api get-bucket-ownership-controls --bucket amzn-s3-demo-bucket. The response shows one ofBucketOwnerEnforced,BucketOwnerPreferred, orObjectWriter— that's your definitive answer for why the upload is or isn't failing. - Check the bucket's ACL, if it has one:
aws s3api get-bucket-acl --bucket amzn-s3-demo-bucket. On a Bucket owner enforced bucket, requests to read ACLs still work — they'll simply show full control assigned to the bucket owner and nothing else, regardless of what the ACL used to say.
If get-bucket-ownership-controls returns an error saying the configuration doesn't exist, that itself is informative on very old buckets, though on current accounts every bucket has an explicit ownership setting from creation.
Setting up CLI access to S3 with an access key
If you're getting this error at all, you already have CLI access working — but it's worth covering properly, since "how do I even get into a bucket from the CLI" is the question right before this one for a lot of readers. With the AWS CLI installed, run aws configure and it prompts for four values in order: AWS Access Key ID, AWS Secret Access Key, a default region name (for example us-west-2), and a default output format.
$ aws configure AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY Default region name [None]: us-west-2 Default output format [None]: json
Those values are written to two local files — the credentials in ~/.aws/credentials and everything else in ~/.aws/config. Access keys are created from the IAM console; AWS's own guidance is that you should avoid the root account's access keys for this and instead create a dedicated IAM user (or better, use IAM Identity Center federation) with only the S3 permissions it needs. If you're managing more than one AWS account or role, aws configure --profile name keeps a second, separate set of credentials you select with --profile on any command.
Once configured, basic bucket access looks like aws s3 ls s3://amzn-s3-demo-bucket to list contents, or aws s3 cp localfile.txt s3://amzn-s3-demo-bucket/ to upload — the higher-level aws s3 commands, as opposed to the lower-level aws s3api commands used throughout this article. s3 commands are simpler for everyday copying and syncing; s3api commands are what you reach for the moment you need to touch a specific setting like ownership controls, bucket policies, or ACLs directly.
When the fix doesn't work
Removing the ACL fixes the ACL error specifically. If something still fails afterward, it's a different error with a different cause:
- Now you get
AccessDeniedinstead: the ACL was never the only thing standing between you and the object — check whether the calling IAM user or role hass3:PutObjecton that bucket, and whether a bucket policy is explicitly denying the request. - Objects upload fine but nobody outside your account can read them: that's expected once ACLs are disabled — public or cross-account read access now has to come from a bucket policy, not an ACL, as covered above.
- A Terraform apply fails on
aws_s3_bucket_acleven after you fixed your CLI script: Infrastructure-as-code tools hit this exact error independently of manual uploads. The fix there is adding an explicitaws_s3_bucket_ownership_controlsresource set toBucketOwnerPreferredorObjectWriterbefore the ACL resource runs, or dropping the ACL resource block entirely if you don't need it. - A CloudFormation stack fails with a similar ACL/ObjectOwnership conflict: the template's
AccessControlproperty and itsOwnershipControlsproperty have to agree — you generally can't set an ACL-basedAccessControlvalue whileOwnershipControlsisBucketOwnerEnforced. - You changed Object Ownership and the error is gone, but files are still not public: changing ownership only re-enables the mechanism — you still need to actually apply an ACL, like
public-read, on the objects you want public, and Block Public Access still has to allow it.
⚠️ What this actually breaks
If you can't reach the S3 console or IAM at all — say, the access key was for a user that's since been deleted — there is no ACL or ownership setting that fixes that. You need valid credentials for a principal with permission on the bucket first; no amount of adjusting Object Ownership substitutes for that.
What this cost Jake, and what fixed it
Jake's shop emails customers a link to their repair receipt, generated as a PDF and uploaded to S3 with the same --acl public-read command his old contractor set up years ago on a different bucket. A new bucket, created this year for a second location, threw the ACL error the first time he tried it — and he lost most of a Saturday assuming his AWS account itself was broken.
The actual fix took two commands: dropping --acl public-read from the upload, and attaching a bucket policy scoped to just the receipts/ prefix instead of the whole bucket. Ethan's point to him afterward was blunt: "The old way made every single file in that bucket a separate decision about who could see it. The new way, you can open one file and see the whole policy for the whole bucket at once." Jake didn't love re-learning a command he thought he already knew — but he also hasn't had to touch that policy since.
🙋♂️ Jake's Reality Check
"Is there any downside to just leaving ACLs disabled everywhere, forever?"
For almost every bucket, no. AWS's own guidance says a majority of modern S3 use cases don't need ACLs at all. The exceptions are narrow: S3 server access logging still has an ACL-based delivery path in some setups, and some long-running cross-account arrangements predate bucket policies. Outside of those, name a concrete reason before you turn ACLs back on — "the old command used one" isn't a reason, it's a habit.
Before you make anything public, check what's actually in it
A bucket policy that grants s3:GetObject with Principal: "*" applies to every object matching that resource pattern, present and future. Before writing one, confirm the prefix you're scoping it to only ever holds files meant for public consumption — receipts, invoices, or anything with a customer's name or address on it should live under a separate, non-public prefix or a separate bucket entirely, not share space with genuinely public assets like a website's images or downloads.
Frequently asked questions
What does AccessControlListNotSupported actually mean?
It means the bucket's Object Ownership setting is Bucket owner enforced, which disables ACLs entirely, and your request tried to set or update one anyway. Requests to read an existing ACL still work; only requests to set or change one fail.
Why did this start happening to a command that used to work?
You're running it against a newer bucket. Since April 27, 2023, S3 automatically applies Bucket owner enforced and full Block Public Access to every newly created bucket. Older buckets keep whatever setting they already had.
Is this an AWS bug or outage I should wait out?
No. It's the intended, documented behavior of the default Object Ownership setting. It won't resolve itself, and it isn't specific to your account or region.
Can I still use ACLs on S3 at all in 2026?
Yes, on any bucket where you set Object Ownership to Bucket owner preferred or Object writer. New buckets just don't start out that way anymore.
What is S3 Object Ownership, in plain terms?
A bucket-level setting that decides two things together: whether ACLs are allowed on that bucket, and who ends up owning an object when someone from another account uploads it.
What's the actual difference between the three Object Ownership settings?
Bucket owner enforced disables ACLs completely and the bucket owner always owns every object. Bucket owner preferred allows ACLs, and objects uploaded with the bucket-owner-full-control ACL transfer ownership to the bucket owner. Object writer allows ACLs and the uploading account always keeps ownership.
How do I check which setting my bucket currently has?
Run aws s3api get-bucket-ownership-controls --bucket your-bucket-name. The response tells you directly which of the three settings is active.
How do I make an object public if ACLs are disabled on my bucket?
Add a bucket policy that grants s3:GetObject to everyone for that object's path, and make sure Block Public Access isn't set to block public bucket policies for that bucket.
Do I need to change Block Public Access settings too, or just the bucket policy?
Both, usually. A bucket policy granting public access has no effect if Block Public Access is still blocking public bucket policies — specifically the BlockPublicPolicy and RestrictPublicBuckets settings need to be off for that bucket.
Will disabling ACLs break files that are already public?
Changing Object Ownership on an existing bucket doesn't retroactively remove existing ACL-based permissions in every case, but it does mean any new object uploaded without a matching bucket policy won't inherit the old ACL-based public access. Confirm current public access is coming from a bucket policy going forward, not an ACL.
Does this error affect Terraform deployments too?
Yes, and for the same underlying reason. A Terraform resource that sets an ACL on a bucket created with the default Object Ownership setting fails the same way the CLI does — add an aws_s3_bucket_ownership_controls resource, or remove the ACL resource if you don't need it.
What if I need different permissions on different objects in the same bucket, the way ACLs used to give me?
A bucket policy can scope permissions by prefix or key pattern, which covers most of what per-object ACLs were used for — grant s3:GetObject only on the specific path, not the whole bucket.
Is bucket-owner-full-control an ACL, and why does it still work?
It is technically an ACL value, but it doesn't grant new access — it only confirms an ownership outcome that's already true under Bucket owner enforced, so S3 allows it through as a no-op.
My upload uses aws s3 sync, not put-object — does the same fix apply?
Yes. aws s3 sync and aws s3 cp accept the same --acl flag and hit the same error on the same buckets; remove or change the ACL argument exactly as described above.
Does an IAM policy matter here too, separate from the bucket policy?
Yes. Access is granted only when nothing in the IAM policy attached to your user or role, and nothing in the bucket policy, denies the request, and at least one of them allows it. Fixing the ACL error doesn't help if the calling identity's IAM policy doesn't permit s3:PutObject or s3:GetObject in the first place.
Should I just re-enable ACLs to make this error go away quickly?
You can, but it's rarely the right move. Removing the ACL argument or switching to a bucket policy fixes the actual problem without reopening a permission mechanism AWS itself now recommends against for most use cases.
- Fixing S3 Access Denied (403) errors
If dropping the ACL leaves you with AccessDenied instead, this walks through the IAM and bucket-policy side of it. - What is Amazon S3 cloud storage?
Newer to S3 entirely? Start here before diving into ownership settings and bucket policies.
Revision note. Written August 2026, covering the current S3 Object Ownership behavior and Block Public Access defaults introduced April 2023. This will need a fresh look if AWS ever changes the default again or renames the console labels. If you've spent your afternoon on a one-line error message that used to be a one-line command, you're not missing something obvious — the ground actually did shift under you, and now you know exactly where.