← All Posts
AWS15 Jul 2026·12 min read

10 AWS S3 Bucket Policy Examples (Copy-Paste)

Srinivasa Rao Maganti — Lead Cloud & DevOps Trainer at CloudTechTrainings

Srinivasa Rao Maganti

Cloud Architect & Lead Trainer, CloudTechTrainings

#S3#Bucket Policy#AWS Security#IAM#Cheatsheet#SAA-C03

Every AWS engineer ends up writing the same handful of S3 bucket policies over and over — enforce HTTPS, let CloudFront in, let another account in, keep everyone else out. This page is the cheatsheet we wish existed the first time: ten ready-to-use policies covering the scenarios that come up in real projects (and in SAA-C03 exam questions), each with a copy-paste JSON block and a short note on the gotcha that usually bites people. Bookmark it, grab what you need, swap in your bucket name, and move on.

Info: Placeholders Used Below

Replace your-bucket-name with your bucket, 111122223333 / 444455556666 with real AWS account IDs, and the example VPC endpoint, distribution, and role names with your own. Every policy here uses Version 2012-10-17 — always keep that line exactly as written; it is the policy language version, not a date to update.

Bucket Policy Anatomy in 60 Seconds

A bucket policy is a resource-based IAM policy attached directly to the bucket. Each statement answers four questions: who (Principal), can or cannot (Effect), do what (Action), on what (Resource) — optionally narrowed by a Condition. Before copying anything below, four rules will save you hours of debugging:

  • Object actions like s3:GetObject need the /* resource (arn:aws:s3:::your-bucket-name/*); bucket actions like s3:ListBucket need the bare bucket ARN. Most "Access Denied" surprises are one of these two missing
  • An explicit Deny always wins — over the bucket policy itself, over IAM policies, over everything. Test Deny statements carefully; you can lock yourself out
  • S3 Block Public Access sits above the bucket policy. A public-read policy does nothing until the relevant block settings are turned off for that bucket
  • Bucket policies max out at 20 KB — if you are hitting the limit, move principal-specific grants into IAM policies and keep the bucket policy for guardrails

Quick Reference — Which Policy Do You Need?

#ScenarioMechanism
1Force all traffic to HTTPSDeny when aws:SecureTransport is false
2Public static website hostingAllow s3:GetObject to everyone
3Only CloudFront can read (OAC)Allow the cloudfront.amazonaws.com service principal, scoped to one distribution
4Another AWS account needs read accessAllow the other account's root ARN as Principal
5Private access via VPC endpoint onlyDeny when aws:SourceVpce does not match
6Office / VPN IP allowlistDeny when aws:SourceIp is outside your CIDR ranges
7Every upload must use SSE-KMSDeny s3:PutObject without the KMS encryption header
8Only my AWS OrganizationDeny when aws:PrincipalOrgID does not match
9One app role gets read/writeAllow a specific IAM role ARN
10Deletes require MFADeny delete actions when MFA is absent

1. Enforce HTTPS-Only Access

The single most common bucket policy in existence, and a baseline requirement in most compliance frameworks. It denies every S3 action made over plain HTTP. Safe to apply to almost any bucket — the SDKs, CLI, and console all use HTTPS by default.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnforceHTTPS",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ],
      "Condition": {
        "Bool": { "aws:SecureTransport": "false" }
      }
    }
  ]
}

2. Public Read for Static Website Hosting

For buckets serving a static website directly (no CloudFront). Grants everyone read access to objects — and only read: no listing, no writes.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForWebsite",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}

Warning: Block Public Access Must Be Off First

New buckets ship with Block Public Access enabled, which silently overrides this policy. Turn off "Block public access granted through new/any public bucket policies" for this one bucket — never at the account level. If the site can sit behind CloudFront instead, use policy #3 and keep the bucket fully private.

3. CloudFront-Only Access (Origin Access Control)

The modern replacement for public website buckets: the bucket stays completely private, and only your CloudFront distribution can fetch objects. This is the policy CloudFront generates when you attach an Origin Access Control (OAC) — the SourceArn condition pins it to one specific distribution.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOAC",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/EDFDVBD6EXAMPLE"
        }
      }
    }
  ]
}

4. Cross-Account Read Access

Lets every principal in a second AWS account (that also has matching IAM permissions on their side) list the bucket and read objects. Using the account root ARN delegates the fine-grained "who exactly" decision to that account's own IAM policies — to grant a single role instead, put the role ARN in Principal (see policy #9).

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CrossAccountRead",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ]
    }
  ]
}

5. Restrict Access to a VPC Endpoint

For buckets that should only ever be touched from inside your VPC — data lakes, internal app storage, anything that must never traverse the public internet. Requests that do not arrive through the named gateway endpoint are denied, no matter whose credentials they carry.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyOutsideVPCEndpoint",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": "vpce-0abcd1234efgh5678"
        }
      }
    }
  ]
}

Warning: This Locks Out the Console — Including You

The AWS console does not go through your VPC endpoint, so this Deny blocks console access to the bucket for everyone, admins included. Before applying, add an escape hatch — e.g. an extra condition excluding your admin role via ArnNotEquals on aws:PrincipalArn — or be prepared to manage the bucket purely from inside the VPC.

6. Allow Only Your Office / VPN IP Range

A simple perimeter for buckets holding internal reports or tooling: any request from outside the listed CIDR ranges is denied. Note that aws:SourceIp checks the public IP AWS sees — traffic arriving through a VPC endpoint carries a private IP and needs policy #5 instead.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OfficeIPAllowlist",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ],
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": [
            "203.0.113.0/24",
            "198.51.100.42/32"
          ]
        }
      }
    }
  ]
}

7. Enforce SSE-KMS Encryption on Every Upload

S3 has encrypted all new objects with SSE-S3 by default since January 2023, so a bare "must be encrypted" policy is now redundant. What teams actually enforce today is the stronger requirement: uploads must use SSE-KMS (customer-managed key, CloudTrail-audited key usage), not just the S3-managed default.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireKMSEncryption",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    }
  ]
}

Tip: Simpler Alternative: Set It as the Bucket Default

If you set the bucket's default encryption to your KMS key, uploads without an encryption header get your key automatically — this policy then only catches uploads that explicitly request the wrong method. Belt and suspenders: do both.

8. Restrict Access to Your AWS Organization

For shared buckets in multi-account setups: any principal from any account inside your AWS Organization can be granted access by their own IAM policies, but nothing outside the org gets in — even if credentials leak. One condition replaces maintaining a list of account IDs.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyOutsideOrganization",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalOrgID": "o-a1b2c3d4e5"
        }
      }
    }
  ]
}

Note: AWS Service Principals Have No Org ID

Services like CloudTrail or ELB logging that write to your bucket as a service principal will be denied by this policy, because service principals carry no aws:PrincipalOrgID. If the bucket receives service-delivered logs, add a second condition — BoolIfExists on aws:PrincipalIsAWSService — to exempt them.

9. Grant One IAM Role Read/Write

The workhorse policy for application buckets: exactly one role — your app server, Lambda execution role, or CI/CD deployer — gets full object CRUD plus listing, and the bucket policy documents that contract explicitly at the bucket itself.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AppRoleReadWrite",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:role/app-server-role" },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ]
    }
  ]
}

10. Require MFA for Deletes

A guardrail for buckets holding backups or critical documents: object deletions are denied unless the caller authenticated with MFA. BoolIfExists makes the condition also catch requests where the MFA key is absent entirely (e.g. long-term access keys).

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireMFAForDelete",
      "Effect": "Deny",
      "Principal": "*",
      "Action": [
        "s3:DeleteObject",
        "s3:DeleteObjectVersion"
      ],
      "Resource": "arn:aws:s3:::your-bucket-name/*",
      "Condition": {
        "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" }
      }
    }
  ]
}

Note: Roles Never Have MFA

IAM roles (including service and Lambda roles) can't present MFA, so this policy blocks all role-based deletions too. Use it on human-managed buckets only — and note it is different from S3 Versioning's "MFA Delete" feature, which is a separate root-user-only versioning setting.

How to Apply a Bucket Policy

Console: bucket → Permissions tab → Bucket policy → Edit → paste → Save. From the CLI, save the JSON to a file and run:

bash
# Apply (overwrites the entire existing policy — merge statements first!)
aws s3api put-bucket-policy \
  --bucket your-bucket-name \
  --policy file://policy.json

# View the current policy
aws s3api get-bucket-policy --bucket your-bucket-name \
  --query Policy --output text

# Remove the policy entirely
aws s3api delete-bucket-policy --bucket your-bucket-name

Warning: put-bucket-policy Replaces, Never Merges

A bucket has exactly ONE policy document. Applying a new one overwrites whatever was there. To combine scenarios from this cheatsheet (e.g. HTTPS-only + CloudFront-only), put multiple statements inside one Statement array, then apply once. Validate first with the IAM Policy Simulator or IAM Access Analyzer — both flag typos and unintended public access before they bite.

Where This Shows Up in Certification Exams

S3 security is heavily tested: the SAA-C03 exam's Secure Applications domain is 30% of your score, and bucket-policy questions — spot the missing /* resource, pick the right condition key, explain why an Allow isn't working under Block Public Access — appear on nearly every attempt. Test yourself against our free SAA-C03 mock exam (60 original scenario questions with explanations), or start with the CLF-C02 Cloud Practitioner mock if you're earlier in the journey. And if you want to build these policies hands-on with a trainer walking you through the gotchas live, our 45-day AWS batch covers S3 security end-to-end with real labs.

Keep Going

Ready to Start Your Cloud Journey?

Live batches Mon–Sat — Azure 9–10 AM IST (started 24 august 2026 — join in progress) · AWS 10:30–11:45 AM IST (starts 31 august 2026). Hands-on labs, exam prep, and community support.

Join Free Demo →WhatsApp Us

Keep Reading

AWS

AWS CLF-C02 Study Guide 2026: How to Pass First Try

A focused study plan for the AWS Cloud Practitioner exam — all 4 scored domains, a realistic 4-week timeline, and exam-day tactics for a certification that assumes zero prior AWS experience.

10 Aug 2026·10 min read
Read →
AWS

AWS SOA-C03 Study Guide 2026: How to Pass First Try

A study plan for the AWS CloudOps Engineer (formerly SysOps Administrator) Associate exam — all 5 domains, an SOA-C03 vs SAA-C03 comparison, and exam-day tactics for an operations-focused certification.

10 Aug 2026·11 min read
Read →