Fix AWS CloudFront 403 ERROR on S3 Origin โ€” OAC Bucket Policy Not Configured

intermediateโ˜๏ธ AWS2026-04-28| AWS CloudFront + S3, OAC (Origin Access Control), any region, AWS Console / AWS CLI v2

Error Message

403 ERROR: The request could not be satisfied. Request blocked. We can't connect to the server for this app or website at this time.
#cloudfront#s3#oac#bucket-policy#403

What's happening

You set up a CloudFront distribution pointing to an S3 bucket, chose Origin Access Control (OAC) as the access method, and now every request hits this wall:

403 ERROR: The request could not be satisfied.
Request blocked. We can't connect to the server for this app or website at this time.

Nothing loads. Direct S3 access works fine when the bucket is public โ€” but CloudFront can't get through.

OAC replaced the older Origin Access Identity (OAI) in 2022. It's more secure, but it requires a specific bucket policy that most tutorials skip or get wrong. Without it, S3 treats every CloudFront request as anonymous and returns 403. That's it. That's the whole problem.

Root cause

When OAC is attached, CloudFront signs requests to S3 using SigV4. S3 needs an explicit bucket policy that says: "allow s3:GetObject only when the request comes from this specific CloudFront distribution." No policy, no access โ€” even if OAC is correctly configured on the CloudFront side.

Common mistakes that trigger this:

  • OAC is set up in CloudFront but the bucket policy was never updated
  • The policy still references an OAI (arn:aws:iam::cloudfront:user/...) instead of the OAC condition
  • Block Public Access is enabled but no policy grants CloudFront access
  • The policy uses the wrong CloudFront distribution ARN

Step 1 โ€” Confirm the distribution is using OAC

Open the CloudFront console โ†’ your distribution โ†’ Origins tab โ†’ click the S3 origin โ†’ check Origin access. It should show Origin access control settings (recommended) with an OAC name. Not "Public", not an OAI.

Via CLI:

aws cloudfront get-distribution-config --id YOUR_DISTRIBUTION_ID \
  --query 'DistributionConfig.Origins.Items[0].S3OriginConfig'

If OriginAccessIdentity is empty and OAC appears under OriginAccessControlId, OAC is active.

Step 2 โ€” Check the current bucket policy

aws s3api get-bucket-policy --bucket YOUR_BUCKET_NAME --query Policy --output text | python3 -m json.tool

Empty output? That's your answer. If you see a principal like arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ..., the policy is still pointing at an OAI โ€” it won't work with OAC.

Step 3 โ€” Apply the correct OAC bucket policy

The fix is a bucket policy that grants s3:GetObject to the CloudFront service principal, locked down to your specific distribution.

First, grab your distribution ARN:

aws cloudfront get-distribution --id YOUR_DISTRIBUTION_ID \
  --query 'Distribution.ARN' --output text

It looks like: arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE

Create the policy file:

cat > bucket-policy.json <<'EOF'
{
  "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::123456789012:distribution/EDFDVBD6EXAMPLE"
        }
      }
    }
  ]
}
EOF

Apply it:

aws s3api put-bucket-policy \
  --bucket YOUR_BUCKET_NAME \
  --policy file://bucket-policy.json

Step 4 โ€” Keep Block Public Access enabled

Don't turn off Block Public Access to "fix" a 403. That's the wrong move. OAC exists precisely so you can keep all four Block Public Access flags ON while CloudFront still serves content.

Verify:

aws s3api get-public-access-block --bucket YOUR_BUCKET_NAME

All four flags should be true. If you already turned them off, turn them back on โ€” the OAC policy handles access without needing public access.

Step 5 โ€” Invalidate the CloudFront cache

CloudFront may have cached the 403 response. Force a refresh:

aws cloudfront create-invalidation \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --paths "/*"

Check the status (typically completes in 30โ€“60 seconds):

aws cloudfront list-invalidations \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --query 'InvalidationList.Items[0].Status'

Verify the fix

Run a quick curl to check response headers:

curl -I https://YOUR_CLOUDFRONT_DOMAIN/your-file.html

You should see HTTP/2 200 with x-cache: Miss from cloudfront on first hit, then Hit from cloudfront on subsequent requests.

Also hit a path you know doesn't exist. S3 should return 404, not 403. A 404 on missing files confirms the policy is working โ€” and not overly permissive.

Prefer clicking over CLI? Use the console shortcut

AWS Console has a built-in shortcut that skips all the manual JSON work. Go to your CloudFront distribution โ†’ Origins โ†’ edit the S3 origin โ†’ scroll to the OAC section โ†’ click "Copy policy". Then open S3 โ†’ Permissions โ†’ Bucket Policy โ†’ paste and save. AWS generates the exact correct policy for your distribution automatically.

Edge cases to check

  • Multi-region setup: S3 bucket in ap-southeast-1, CloudFront edge in us-east-1? Make sure the OAC signing behavior is set to Sign requests (recommended) โ€” not "Do not sign".
  • SSE-KMS encrypted objects: Customer-managed KMS keys require an extra step. CloudFront needs kms:Decrypt in the KMS key policy. OAC supports this, but you have to add it explicitly.
  • Leftover OAI statements: If the old OAI policy is still there, remove it. Having both an OAI and OAC statement in the same policy won't break anything, but it will confuse everyone on your team โ€” including future you.

Related Error Notes