CloudFront 403 with S3: OAC Troubleshooting Guide
Create Time:2026-07-31 14:35:49
浏览量
1086

A 403 error from Amazon CloudFront with an Amazon S3 origin does not always mean that the bucket needs to be public. Common causes include an Origin Access Control that is not attached to the active origin, an incorrect distribution ARN in the bucket policy, the wrong S3 endpoint type, a missing or incorrectly cased object key, an invalid default root object, missing AWS KMS permissions, or a previously generated 403 response that remains in the CloudFront cache.

The safest approach is to identify which layer is returning the denial before changing permissions. For a private S3 origin, the recommended architecture is to keep S3 Block Public Access enabled and grant a specific CloudFront distribution access through Origin Access Control, or OAC.

Configuration details in this article were reviewed on July 31, 2026. Account IDs, bucket names, distribution IDs, domains, and object paths in the examples are placeholders and must be replaced before use.

First, Identify the Type of 403 Error

Use the failure pattern to narrow the investigation:

  1. Every object returns 403: Check the origin type, OAC attachment, S3 bucket policy, explicit Deny statements, and KMS key policy.

  2. The root URL returns 403, but /index.html works: Check the CloudFront default root object.

  3. Only one file or directory returns 403: Check whether the object exists, its exact capitalization, the origin path, ownership, and encryption.

  4. The configuration was fixed, but the old 403 remains: Check the error caching TTL and invalidate the affected object if necessary.

  5. Only some viewers receive 403: Check geographic restrictions, AWS WAF, signed URLs, signed cookies, and viewer access restrictions.

Begin by requesting the distribution root and one or two objects that you know exist:

curl -I https://cdn.example.com/
curl -I https://cdn.example.com/index.html
curl -I https://cdn.example.com/assets/app.css

Record the status code and headers such as server, x-cache, via, and x-amz-cf-id. If a specific object works but the root URL fails, changing the bucket policy should not be your first action.

Step 1: Confirm the S3 Origin Type

CloudFront can connect to S3 in two fundamentally different ways:

  • Regular S3 bucket REST endpoint: Supports OAC and private bucket access. This is the model covered by most of this guide.

  • S3 static website endpoint: Must be configured as a custom origin. OAC and the older OAI mechanism do not work with an S3 website endpoint, and the website content normally needs to be publicly readable.

If the goal is to prevent direct public access to S3 and allow viewers to retrieve content only through CloudFront, use a regular S3 bucket origin with OAC rather than the S3 website endpoint.

Do not disable S3 Block Public Access simply to make the 403 disappear. If the intended design uses a private origin, making the bucket public can hide the real OAC problem and increase the risk of unintended data exposure.

Step 2: Verify That OAC Is Attached to the Correct Origin

Creating an OAC is not enough. It must be attached to the exact origin used by the active cache behavior.

  1. Open the relevant distribution in the CloudFront console.

  2. Open the Origins tab and edit the S3 origin that serves the failing request.

  3. Select Origin Access Control in the origin access settings.

  4. Confirm that the intended OAC is selected.

  5. Use the recommended signing behavior: always sign origin requests.

  6. Save the configuration and wait until the distribution finishes deploying.

With the recommended signing option, CloudFront uses Signature Version 4 to sign requests sent to S3. If signing is disabled, a private bucket will not grant access merely because an OAC resource exists.

Also inspect the cache behaviors. A distribution can have multiple origins, and a path pattern such as /assets/* may route to a different origin than the default behavior. Troubleshooting the wrong origin will not fix the request.

Step 3: Check the S3 Bucket Policy

For a read-only OAC configuration, the bucket policy normally allows the CloudFront service principal to perform s3:GetObject and restricts the permission to a specific distribution through AWS:SourceArn.

A minimal read-only policy can start with this structure:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontReadOnly",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
        }
      }
    }
  ]
}

Check the following details carefully:

  • The Resource must match object ARNs: Reading files normally requires arn:aws:s3:::BUCKET_NAME/*, not only the bucket ARN.

  • The account and distribution IDs must be correct: Do not insert the OAC ID, origin ID, or CloudFront domain in place of the distribution ID.

  • The Principal must match OAC: OAC uses the CloudFront service principal. An OAI IAM ARN belongs to the legacy access model.

  • Look for explicit Deny statements: A Deny in the bucket policy, an AWS Organizations service control policy, or another applicable policy overrides an Allow.

  • Check prefix restrictions: A policy that permits only public/* will still reject requests for objects outside that prefix.

During a migration from Origin Access Identity to Origin Access Control, AWS supports temporarily allowing both identities. Keep the existing OAI permission while the OAC configuration deploys, then remove the OAI statement after confirming that requests succeed through OAC.

Step 4: Verify the Object Key, Capitalization, and Origin Path

S3 object keys are case-sensitive. The keys index.html, Index.html, and INDEX.HTML refer to different objects. An incorrect prefix, capitalization difference, or unexpected slash can produce an AccessDenied response.

Use the AWS CLI to verify that the object exists:

aws s3api head-object \
  --bucket YOUR_BUCKET_NAME \
  --key index.html

If the CloudFront origin has an Origin Path, include it when calculating the final S3 key. For example, if the origin path is /production and the viewer requests /index.html, CloudFront may request production/index.html from the bucket.

Check S3 Object Ownership as well. New buckets generally use Bucket owner enforced, which disables ACLs and manages access through policies. This setting is appropriate for most OAC configurations. However, objects uploaded by another AWS account under an older ACL-based workflow can still create ownership and access problems.

Step 5: Check the Default Root Object

If https://cdn.example.com/index.html works but https://cdn.example.com/ returns 403, the most likely problem is a missing or invalid default root object.

Set the distribution’s default root object to:

index.html

Do not configure it as:

/index.html

AWS documentation specifies that the default root object must not begin with a forward slash. Using /index.html can result in a 403 Access Denied response.

The default root object primarily applies to the distribution root. If it is set to index.html, a request for /docs/ is not automatically rewritten to /docs/index.html. Directory-style routing requires a URI rewrite, application routing, or another explicit design. A CloudFront Function can perform this rewrite, but test the resulting cache keys and redirects before deployment.

Step 6: Check SSE-KMS Permissions

Objects encrypted with SSE-S3 normally do not require a separate KMS permission. Objects encrypted with SSE-KMS using a customer-managed AWS KMS key can still return 403 even when the bucket policy allows s3:GetObject.

The KMS key policy must allow the CloudFront service principal to use the key and should restrict access to the intended distribution ARN. Read access commonly requires kms:Decrypt. Workflows that write objects through CloudFront may also require kms:Encrypt and kms:GenerateDataKey*.

If only some objects fail and those files use a different KMS key, inspect the Server-side encryption setting of a failing object before changing the general bucket policy.

Step 7: Clear a Cached 403 After Fixing Permissions

CloudFront caches some error responses. The default error caching duration is commonly 10 seconds, but a custom error response can set a longer Error Caching Minimum TTL. Origin response headers can also affect how long the error remains cached.

First, wait for the distribution configuration to finish deploying and for the error cache to expire. If immediate verification is necessary, invalidate the affected path:

aws cloudfront create-invalidation \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --paths "/index.html"

Invalidating only the affected object is usually safer than using /*. Expand the invalidation scope only when the cached failure affects many paths.

For S3 origins, CloudFront may retain an error for at least one second even when the configured Error Caching Minimum TTL is zero. This behavior helps protect the origin from repeated requests.

Other CloudFront Features That Can Return 403

If the S3 object and OAC permissions are correct, check whether CloudFront itself is denying the viewer request:

  • AWS WAF: A custom rule, rate-based rule, or managed rule group may block the request.

  • Geographic restrictions: Viewers in blocked countries or territories receive 403 responses.

  • Signed URLs or signed cookies: When Restrict viewer access is enabled, a missing, invalid, or expired signature results in 403.

  • Forwarded Host header: Forwarding an unsuitable viewer Host header to an S3 REST endpoint can cause origin errors.

  • Stacked CloudFront distributions: AWS does not recommend placing one CloudFront distribution behind another, and the configuration may return 403.

  • Disallowed HTTP methods: A behavior that permits only GET and HEAD will not process PUT, POST, PATCH, or DELETE as expected.

Recommended CloudFront S3 403 Troubleshooting Order

  1. Request the root URL and a known existing object to define the failure scope.

  2. Confirm that the object key exists and check capitalization, prefixes, and Origin Path.

  3. Verify that the origin is a regular S3 bucket endpoint rather than an unintended website endpoint.

  4. Confirm that OAC is attached to the active origin and configured to always sign requests.

  5. Check the bucket policy Principal, Resource, account ID, and distribution ID.

  6. Look for explicit Deny statements, ownership issues, and cross-account uploads.

  7. If the object uses SSE-KMS, inspect the KMS key policy.

  8. If only the root URL fails, check the default root object and remove any leading slash.

  9. Check AWS WAF, geographic restrictions, signed access, and allowed HTTP methods.

  10. Wait for deployment to complete and clear only the necessary cached errors.

Frequently Asked Questions

Must I Make the S3 Bucket Public to Fix CloudFront 403?

No. A regular S3 bucket can keep Block Public Access enabled. Use OAC and a bucket policy limited to the specific CloudFront distribution to grant private object access.

Should I Use OAC or OAI?

Use OAC for new configurations. AWS recommends OAC because it supports all S3 Regions, SSE-KMS, and dynamic S3 requests. OAI is the legacy mechanism and is mainly relevant to existing distributions.

Why Can I See the File in the S3 Console While CloudFront Gets 403?

The S3 console uses the IAM permissions of your signed-in user or role. CloudFront uses its OAC-signed request and the bucket policy. These are different identities, so administrator access in the console does not prove that CloudFront has permission.

Why Does a Missing S3 Object Sometimes Return 403 Instead of 404?

A private S3 origin does not always reveal whether an object exists. AWS troubleshooting guidance notes that a missing object or an incorrectly capitalized object name can appear as AccessDenied.

Why Does /docs/ Not Load docs/index.html?

The CloudFront default root object does not automatically apply to every subdirectory. Directory index behavior requires a URI rewrite, application route, or a suitable static website architecture.

How Long Does a Permission Fix Take to Work?

S3 and KMS policy changes are usually applied quickly, but a CloudFront distribution change must finish deploying globally. A previous 403 may also remain until its error caching TTL expires. Wait for deployment before invalidating the affected object.

Conclusion

When CloudFront returns 403 from an S3 origin, first determine whether every object fails, only the root path fails, one object fails, or a previous error is still cached. For private content, the expected design is usually a regular S3 bucket with Block Public Access enabled, an OAC that always signs requests, and a bucket policy that grants read access only to the intended CloudFront distribution.

If a specific file works while the root path fails, fix the default root object. If only encrypted files fail, inspect the KMS key policy. If the permissions are correct but the error remains, allow the error cache to expire or invalidate the affected path. This sequence avoids the risky shortcut of making the bucket public.

CloudFlew provides CDN services based on AWS CloudFront. When deploying a private S3 origin, review the CloudFront origin, OAC, S3 bucket policy, default root object, encryption, and cache behavior as one complete access chain.

References

  1. AWS CloudFront Developer Guide: Restrict access to an Amazon S3 origin, reviewed July 31, 2026

  2. AWS CloudFront Developer Guide: Specify a default root object, reviewed July 31, 2026

  3. AWS CloudFront Developer Guide: Troubleshoot distribution issues, reviewed July 31, 2026

  4. AWS CloudFront Developer Guide: HTTP 403 Permission Denied, reviewed July 31, 2026

  5. AWS CloudFront Developer Guide: Control how long CloudFront caches errors, reviewed July 31, 2026