How to Fix: AWS S3 AccessDenied 403

AWS403High severity

The Scenario

You’re deploying a static site via an S3 bucket fronted by CloudFront. The CI/CD pipeline pushes assets fine, but the CloudFront distribution starts returning 403 errors after a recent IAM role update. Users see a blank page, and your monitoring alerts start firing on a busy Friday afternoon.

Symptoms

Root Cause

The S3 bucket policy, IAM role, or bucket ACL is denying the request. This usually stems from an explicit Deny in a bucket policy, a missing s3:GetObject permission on the IAM role, or the S3 Block Public Access settings overriding a permissive policy. CloudFront adds another layer — the Origin Access Control (OAC) or Origin Access Identity (OAI) must be correctly configured and attached.

Resolution (Step-by-Step)

  1. Check the effective permissions for the principal
    Use the AWS Policy Simulator with the exact IAM role/ARN and the S3 action (e.g., s3:GetObject).

    aws iam simulate-principal-policy \
      --policy-source-arn arn:aws:iam::123456789012:role/my-app-role \
      --action-names s3:GetObject \
      --resource-arns arn:aws:s3:::my-bucket/*
  2. Inspect the S3 bucket policy
    Look for explicit Deny statements or missing Allow for the principal.

    aws s3api get-bucket-policy --bucket my-bucket
  3. Verify S3 Block Public Access settings
    If you need public access, ensure BlockPublicAcls, BlockPublicPolicy, IgnorePublicAcls, and RestrictPublicBuckets are all set to false.

    aws s3api get-public-access-block --bucket my-bucket
  4. For CloudFront: Validate the OAC/OAI configuration
    Ensure the CloudFront distribution’s origin is set to use an OAC (recommended) or OAI, and the S3 bucket policy allows that identity.

    aws cloudfront get-distribution-config --id E1234567890ABC

    Then check the bucket policy includes a statement like:

    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1234567890ABC"
        }
      }
    }
  5. Check IAM role trust policy and permissions boundary
    If the principal is an IAM role, confirm the trust policy allows the service (e.g., EC2, Lambda) to assume it, and no permissions boundary restricts S3 access.

Why This Sometimes Doesn’t Work

The most common trap is fixing the bucket policy or IAM role but forgetting about the S3 Block Public Access settings at the account level. Even if the bucket-level settings are permissive, the account-level BlockPublicAcls or RestrictPublicBuckets can silently override them. Always check both the bucket and account-level configurations. Another gotcha: CloudFront OAC requires the s3:GetObject action and the s3:GetObjectVersion action if you’re using versioning — missing that second action causes a 403.

Verification

Run a HEAD request on an object to confirm access:

curl -I https://my-bucket.s3.amazonaws.com/file.txt
# Expected: HTTP/2 200

Or use the AWS CLI with the exact principal:

aws s3api head-object --bucket my-bucket --key file.txt
# Expected: no error, returns metadata

Common Follow-up Questions

Q: Why does the error only happen for some objects?
A: Check the object-level ACLs or if the bucket policy uses Principal conditions that exclude certain paths. Also verify that object keys don’t contain special characters that break the policy.

Q: Does enabling S3 Transfer Acceleration affect permissions?
A: No, Transfer Acceleration only changes the endpoint, not the access control. If you get a 403, the issue is still in your IAM/policy config.

Q: How do I debug CloudFront-to-S3 403 specifically?
A: Temporarily bypass CloudFront and access the S3 object directly. If that works, the issue is in the CloudFront origin (OAC/OAI) or the bucket policy’s CloudFront-specific statement.

Prevent This in the Future

Set up proactive monitoring on your S3 bucket policies and IAM role changes to catch permission drift before it hits users. Use a tool like Better Stack to monitor CloudFront error rates and S3 access logs, alerting you when 403s spike above a threshold.
Set up uptime monitoring and alerts with Better Stack