AWS S3 Bucket Public Exposure: Fix & Lock It Down

Server & Cloud Intermediate 👁 0 views 📅 May 26, 2026

Your S3 bucket just got flagged as public. Here's how it happened and exactly how to make it private again.

You get an email from AWS Trusted Advisor or see a red flag in the S3 console: "This bucket is publicly accessible." Maybe you were setting up a static website and forgot to tighten permissions. Or a junior admin clicked "Make public" on an object. Or you applied a bucket policy that accidentally grants GetObject to Principal: *. The trigger is almost always a human mistake — someone enabled public ACLs or wrote a policy with too broad a scope.

The culprit here is almost always one of two things: the bucket's ACL allows public read/write, or the bucket policy has a statement that grants anonymous access. AWS treats them as separate controls, so you need to check both. Don't bother checking CloudTrail logs first — nine times out of ten it's one of these two settings.

Root Cause

By default, new S3 buckets are private and block public access. But if you or someone on your team explicitly:

  • Enabled ACLs and granted Everyone read access
  • Saved a bucket policy with "Effect": "Allow" and "Principal": "*"
  • Unchecked the "Block all public access" settings (often to host a website)
...you've opened the door. AWS does not warn you until after the fact unless you have Trusted Advisor enabled (free tier only gives limited checks).

Fix It: Step-by-Step

  1. Go to the S3 console and select the bucket. Click the Permissions tab.
  2. Block all public access. Click "Block public access (bucket settings)" and enable all four checkboxes. Save. This overrides any ACLs or policies — it's the nuclear option and it works.
  3. Remove the bucket policy. If you still need a policy (e.g., for CloudFront), go to "Bucket policy" and delete any statement that has "Principal": "*". Replace it with a policy that restricts access to specific IAM roles or an origin access identity (OAI). Example of a safe policy for CloudFront:
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E1234567890"
          },
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::your-bucket-name/*"
        }
      ]
    }
  4. Check ACLs. In the Permissions tab, scroll to "Access control list (ACL)". If you see "Everyone (public access)" with any checkmarks, click edit and uncheck all of them. Save.
  5. Verify in the console. The bucket's Public access setting should show "Objects can be public" as "Off" and "Buckets can be public" as "Off".

If It Still Fails

Sometimes even after blocking public access, the console still shows the bucket as public. This happens when the bucket has objects with public ACLs set individually. S3 bucket-level settings don't retroactively fix object ACLs — that's a separate issue.

To fix that, run this AWS CLI command from your terminal (make sure you have the right profile and region):

aws s3api list-objects --bucket your-bucket-name --query 'Contents[].{Key: Key}' --output text | xargs -I {} aws s3api put-object-acl --bucket your-bucket-name --key {} --acl private

This iterates through all objects and sets them to private. For buckets with millions of objects, write a small Python script with boto3 and batch processing — the CLI will be too slow.

Also check if the bucket is referenced in CloudFormation or Terraform. If your IaC template has PublicAccessBlockConfiguration set to false, the bucket will re-open on the next deployment. Fix the template first, then apply.

One more thing: if this bucket is supposed to serve public content (like images for a website), don't use ACLs or public bucket policies. Use CloudFront with an OAI, or a signed URL. That's the real fix — never expose the bucket directly.

Was this solution helpful?