An S3 bucket goes public when one of four things happens: a legacy public ACL, a bucket policy with a wildcard principal, Block Public Access switched off, or an object uploaded with a public ACL. The fix that covers all four is to enforce Block Public Access at the account level, disable ACLs with BucketOwnerEnforced, and share files with presigned URLs instead of public links.
Keep every S3 bucket private by default, block public access at the account level, and check the three settings that actually leak data: bucket policies, access control lists, and the public access block. Most public S3 exposures are not hacks — they are small configuration mistakes, and each one takes under a minute to fix once you know where to look.
Amazon S3 has leaked data for startups, hospitals, and government agencies alike, and the pattern is almost always the same: a bucket that was meant to be private ended up readable by anyone with a link. The fixes are simple, free, and mostly one-time — this guide walks through the mistakes that cause public S3 buckets, how to find them, and how to lock your buckets down for good.
How Do S3 Buckets End Up Public in the First Place?
An S3 bucket is private by default. Nothing makes it public except a setting someone changed — usually with good intentions. The most common story: a developer needs to share a file or serve images on a website, so they loosen a permission to make it work, and the bucket stays open long after the task is done.
The second common story is inheritance. A bucket created years ago may still carry an old access control list or a policy written before AWS added stronger defaults. AWS has tightened its defaults several times — new buckets now block public access automatically — but older buckets keep whatever settings they were born with unless someone updates them.
What Are the Most Common Mistakes That Expose S3 Buckets?
These six mistakes cause nearly all public S3 exposures. Check your buckets against each one.
1. A bucket policy with “Principal”: “*”. A bucket policy that grants access to the wildcard principal makes the bucket readable by the entire internet. This often appears in policies copied from tutorials about hosting a static website. If your bucket is not actually a public website, remove the wildcard grant.
2. Public access control lists (ACLs). Older buckets may grant “Everyone” read access through an ACL — the legacy permission system that predates bucket policies. ACLs are easy to miss because they live on a separate tab in the console, and a single “AllUsers” read grant is enough to expose everything in the bucket.
3. Public Access Block turned off. AWS provides a four-part “Block Public Access” setting at account and bucket level. If it is off, nothing stops a public policy or ACL from taking effect. Keep it on at the account level so no single bucket can override it by accident.
4. Pre-signed URLs shared too widely. A pre-signed URL grants temporary access to a private object, which is legitimate. The mistake is treating it as permanent: posting it in a public chat, committing it to a public repository, or setting an expiry of days instead of minutes.
5. Static website hosting enabled by default thinking. Enabling static website hosting on a bucket makes its contents web-accessible by design. Teams sometimes enable it for one HTML file and forget that every object in the bucket — including backups and uploads — becomes reachable too.
6. Cross-account access without limits. Granting another AWS account access to your bucket is normal in partnerships, but a grant written too broadly — or to an account you no longer work with — is an exposure waiting to happen. Review cross-account grants at least twice a year.
How Can You Check Whether Your Buckets Are Public Right Now?
Run this five-minute self-audit in the AWS console. It costs nothing and catches the majority of exposures.
Step 1 — Open the S3 console and look at the Access column. Every bucket shows an access label. Any bucket marked “Public” needs immediate attention; “Objects can be public” means the settings allow exposure even if nothing is public yet.
Step 2 — Turn on account-level Block Public Access. Go to S3, then “Block Public Access settings for this account”, and enable all four blocks. This is the single most effective setting in S3 security, and it protects buckets you have not created yet.
Step 3 — Check each bucket’s Permissions tab. Open the bucket policy and the ACL. Look for “Principal”: “*” in the policy and “Everyone” or “Authenticated users” in the ACL. Remove anything that grants access to the public unless the bucket is deliberately a public website.
Step 4 — Run AWS Trusted Advisor or Access Analyzer. Both are free at the basic level. Trusted Advisor flags buckets with open access permissions; Access Analyzer lists buckets shared outside your account. Run both and clear every finding.
Step 5 — Check CloudTrail for surprises. Search CloudTrail for PutBucketPolicy and PutBucketAcl events in the last 90 days. If someone changed a bucket’s permissions recently, you will see exactly who and when.
Repeat this audit quarterly — buckets drift open as people solve short-term problems. Pair it with our cloud security checklist for small teams.
What Is the Right Way to Share Files From S3?
Sometimes a file genuinely needs to be public — a product image, a downloadable PDF, a public dataset. The safe way is to make the sharing deliberate and narrow, not to open the whole bucket.
For website assets, serve them through CloudFront with Origin Access Control instead of making the bucket public. CloudFront fetches from the private bucket and serves the files to visitors; nobody can reach the bucket directly. For one-off sharing, generate a pre-signed URL with a short expiry — minutes or hours, never days.
For public datasets or downloads, use a dedicated bucket holding only the public files. Keep private data in separate buckets so a policy mistake on the public bucket can never expose it — the same principle behind the shared responsibility model: AWS secures the infrastructure, but deciding what is public is your job.
How Do You Prevent Buckets From Going Public Again?
Fixing a public bucket once is easy. Keeping it private takes four controls that do most of the work.
Enforce Block Public Access with a policy. Use an AWS Organizations service control policy, or an IAM policy that denies s3:PutBucketPublicAccessBlock changes, so nobody can switch the protection off without going through a deliberate approval.
Alert on changes. Set up an EventBridge rule or a Config rule that notifies your team whenever a bucket policy changes or public access is enabled. A Slack or email alert within minutes turns a mistake into a quick fix instead of a breach discovered months later.
Use least-privilege IAM roles. The people and applications that write to S3 should have only the permissions they need. A developer who cannot change bucket policies cannot accidentally make a bucket public.
Scan continuously. Enable AWS Config’s s3-bucket-public-read-prohibited and s3-bucket-public-write-prohibited rules, or a third-party scanner, so every new bucket is evaluated automatically. Combine this with a tested recovery plan — our disaster recovery plan template covers what to do when data is exposed, not just when it is lost.
The four Block Public Access settings, and which one saves you
Block Public Access is not one switch — it is four independent settings, and the ranking pages all list them because each one blocks a different mistake. BlockPublicAcls rejects new public ACLs. IgnorePublicAcls makes S3 ignore public ACLs that already exist. BlockPublicPolicy rejects bucket policies that grant public access. RestrictPublicBuckets limits access on buckets that are already public to AWS service principals and authorised users. Turn all four on at the account level, not just the bucket level: account-level settings override anything more permissive set on a single bucket.
| Setting | What it blocks | Where to set it |
|---|---|---|
| BlockPublicAcls | Rejects new public ACLs | Account and bucket |
| IgnorePublicAcls | Ignores public ACLs that already exist | Account and bucket |
| BlockPublicPolicy | Rejects bucket policies that grant public access | Account and bucket |
| RestrictPublicBuckets | Limits access on buckets that are already public to AWS service principals and authorised users | Account and bucket |
Stop sharing files with public links — use presigned URLs
The most common reason a bucket is made public is embarrassingly ordinary: someone needs to share one file, so the whole bucket gets opened. The right pattern is a presigned URL — a short-lived, signed link generated on your backend after the user is authenticated. It expires in minutes, it works for uploads as well as downloads, and the bucket itself stays private. Never fix an upload error by setting a bucket to public-read; that permission applies to every future file uploaded to the same bucket, not just the one you were trying to share.
Three settings that make a leak structurally impossible
First, enforce BucketOwnerEnforced ownership on every bucket. It disables ACLs entirely, so an object uploaded with a public ACL cannot leak — the single most important change on an existing bucket. Second, turn on versioning and MFA Delete on buckets that hold anything you cannot afford to lose; without versioning, one bad delete is permanent. Third, use IAM Access Analyzer to flag overly permissive policies, and add a Service Control Policy that denies turning Block Public Access off, so no developer or automation can silently disable it.
The 10-minute audit
| Check | Console or CLI | Pass looks like |
|---|---|---|
| Account-level Block Public Access on | S3 console → Block Public Access or get-public-access-block | All four settings on |
| Bucket-level Block Public Access on | S3 console → bucket Permissions, or get-public-access-block | All four settings on for the bucket |
| BucketOwnerEnforced set | S3 console → Object Ownership, or get-bucket-ownership-controls | Object Ownership is Bucket owner enforced |
| No Principal “*” without a Condition | S3 console → bucket policy, or get-bucket-policy | No wildcard principal without a Condition |
| No public-read ACLs | S3 console → bucket ACL, or get-bucket-acl | No grants to Everyone or Authenticated Users |
| Versioning on | S3 console → bucket Properties, or get-bucket-versioning | Status: Enabled |
| CloudTrail data events on for sensitive buckets | CloudTrail console → Trails, or get-trail | Data events logging S3 objects |
| Access Analyzer findings at zero | IAM Access Analyzer console | Zero findings for the account |
For a fuller walkthrough, see our step-by-step guide to preparing for a cloud security audit.
Frequently Asked Questions
How do I know if my S3 bucket is public?
Open the S3 console and check the Access column: “Public” means anyone on the internet can read it. Also check the bucket’s Permissions tab for a policy with “Principal”: “*” or an ACL granting “Everyone” read access, and run S3 Access Analyzer to confirm.
Can I make an S3 bucket private again after it was public?
Yes. Enable Block Public Access on the bucket and at the account level, remove any public bucket policy or ACL grant, and the bucket is private immediately. Then check your access logs to see whether anyone downloaded files while it was open.
Does Block Public Access break my website hosted on S3?
It can, if the website relies on the bucket being directly public. The fix is to serve the site through CloudFront with Origin Access Control, which keeps the bucket private while the website still works for visitors.
What is the difference between a bucket policy and an ACL?
A bucket policy is a JSON document attached to the bucket that controls access in detail; an ACL is the older, simpler permission list attached to buckets and objects. Both can make a bucket public, so you must check both — and AWS now recommends disabling ACLs entirely and using policies only.
Is there a command to check every bucket in my account at once?
Yes — IAM Access Analyzer lists publicly shared resources across the whole account in one place, and you can loop get-public-access-block and get-bucket-policy-status over every bucket with the AWS CLI.
What is the safest way to host a static website on S3?
Keep the bucket private and serve the site through CloudFront with Origin Access Control, which accesses the bucket as an AWS service principal that Block Public Access allows. Only use a public bucket in a dedicated account that holds nothing sensitive.
Does Block Public Access break CloudFront?
No. CloudFront with Origin Access Control accesses the bucket as an AWS service principal, which Block Public Access allows. The older Origin Access Identity does not support the same signing, so use OAC for new setups.
What does BucketOwnerEnforced actually change?
It disables ACLs entirely on the bucket, so an object uploaded with a public ACL cannot leak. AWS recommends it as the modern default, managing access with bucket policies and IAM only.
