AWS S3 Bucket Policy Generator

Pick an access pattern, get a bucket policy with the guardrails already in it: HTTPS required, and optionally SSE-KMS, a VPC endpoint or an organization restriction. Every version is checked by our own analyzer before you copy it.

Bucket
Who gets access
Guardrails

Deny statements always win, whatever else the policy or an IAM policy allows. These are the ones worth having on every bucket.

bucket-policy.json

updates as you type

    Common mistakes

    These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

    1. Making a bucket public to serve static files

      Principal * grants to the entire internet, and the bucket then also answers ListBucket if that is permitted. Almost every S3 leak starts here.

      Instead:Use CloudFront with origin access control and keep the bucket private.

    2. Omitting the deny on insecure transport

      Without an explicit Deny on aws:SecureTransport false, the bucket accepts plain HTTP.

      Instead:Add that Deny to every bucket policy as a matter of course.

    3. Granting on the bucket ARN when objects were meant

      arn:aws:s3:::bucket covers bucket-level actions and arn:aws:s3:::bucket/* covers objects. GetObject on the bucket ARN grants nothing.

      Instead:List both, with the right actions on each.

    The four patterns

    Almost every bucket policy is one of these. The order they appear in the dropdown is deliberate: the safe answer is at the top, and the one people reach for by habit is at the bottom.

    Private, with guardrails

    No grants at all, only the Deny statements: HTTPS required, and optionally SSE-KMS, a VPC endpoint, or your organization. This is the right starting point for almost every bucket, because access comes from IAM policies on the identities that need it rather than from the bucket.

    CloudFront with Origin Access Control

    Grants s3:GetObject to the CloudFront service principal, pinned to your distribution ARN. This is how public content should be served: the bucket is not directly addressable, you get caching and TLS, and the origin cannot be enumerated.

    Cross-account

    Names accounts or role ARNs explicitly, with read by default and write only if you ask. A bare 12-digit account id is expanded to its root ARN, which trusts the account to delegate rather than a specific role.

    Public read

    Listed last on purpose, and it never includes ListBucket. It also cannot take effect while the public access block is on, which is a safety net you have to deliberately remove first. The generator says so rather than letting you find out.

    Deny statements always win

    The guardrails in this generator are Deny statements, and that matters more than it looks. An explicit Deny beats every Allow anywhere: in this policy, in an IAM policy attached to a role, in a permissions boundary, in anything a future engineer adds.

    That is what makes them worth having on a bucket you believe is already private. They are not describing today's access, they are constraining every change that comes after, including the ones nobody reviews carefully at four in the afternoon during an incident.

    The same property is why the VPC endpoint and organization options come with a warning. A Deny that excludes your own access path locks you out just as effectively as it locks out anyone else.