AWS & Cloud ← Back to blog

Making CMS Images Publicly Readable in S3

13 May 2026 · Matt

The specific S3 bucket policy and PublicAccessBlock settings needed to serve a cms-images/ prefix publicly.

The Challenge

S3 buckets default to fully private. Making images publicly readable requires threading through two layers of access control: the bucket's Public Access Block settings and the bucket policy itself.

PublicAccessBlock Configuration

Even with a public bucket policy, AWS blocks public access by default if the bucket-level block is enabled. The BlockPublicPolicy and RestrictPublicBuckets flags must be False, while ACL-related blocks can stay True (we're using a policy, not ACLs).

def _ensure_public_cms_images():
    s3 = _boto_s3()
    s3.put_public_access_block(
        Bucket=S3_BUCKET,
        PublicAccessBlockConfiguration={
            "BlockPublicAcls":       True,
            "IgnorePublicAcls":      True,
            "BlockPublicPolicy":     False,   # must be False
            "RestrictPublicBuckets": False,   # must be False
        },
    )
    policy = json.dumps({
        "Version": "2012-10-17",
        "Statement": [{
            "Sid":       "PublicReadCmsImages",
            "Effect":    "Allow",
            "Principal": "*",
            "Action":    "s3:GetObject",
            "Resource":  f"arn:aws:s3:::{S3_BUCKET}/cms-images/*",
        }],
    })
    s3.put_bucket_policy(Bucket=S3_BUCKET, Policy=policy)

Why Prefix-Scoped

The bucket also stores private uploads from the media manager app. The policy resource ARN ends in /cms-images/* — only that prefix is public. User uploads under images/, documents/ etc. remain private.

Important: Call _ensure_public_cms_images() before uploading images, not after. The upload succeeds either way, but the image won't be publicly accessible until the policy is in place.