Troubleshooting CloudFront 403 Forbidden and S3 Access Denied Errors
Comprehensive guide to fixing CloudFront 403 Forbidden and S3 Access Denied errors. Learn to configure OAC/OAI, bucket policies, KMS keys, and WAF rules.
- Origin Access Control (OAC) or Origin Access Identity (OAI) misconfigurations are the #1 cause of CloudFront 403s when backed by S3.
- Missing 'Default Root Object' causes a 403 Access Denied instead of a 404 Not Found when users hit the base domain without a specific URI.
- AWS KMS encryption requires explicitly granting the CloudFront Service Principal `kms:Decrypt` permissions on the S3 bucket's KMS key.
- Quick Fix: Validate your S3 Bucket Policy explicitly allows `s3:GetObject` for the CloudFront distribution ARN.
| Symptom / Scenario | Root Cause | Resolution Method | Risk Level |
|---|---|---|---|
| 403 on root domain (e.g., example.com) but sub-paths work | Missing Default Root Object | Set Default Root Object to 'index.html' in CloudFront distribution settings. | Low |
| Raw XML 'AccessDenied' from S3 through CloudFront | Bucket Policy or OAC Misconfiguration | Update S3 Bucket Policy to allow CloudFront Service Principal. | Medium |
| 403 Forbidden with 'Request could not be satisfied' HTML page | AWS WAF Blocking Request | Check WAF WebACL sampled requests and adjust rules/geo-blocks. | Medium |
| 403 on newly uploaded encrypted objects | KMS Key Policy missing CloudFront permissions | Add 'kms:Decrypt' for CloudFront ARN in the KMS Key Policy. | High |
Understanding the CloudFront 403 Forbidden Error
When deploying modern web applications, combining Amazon CloudFront as a Content Delivery Network (CDN) with Amazon S3 as an origin is a foundational architecture. However, this pairing frequently leads to the dreaded 403 Forbidden or Access Denied errors. If you are a DevOps engineer or SRE, you have likely encountered the message: The request could not be satisfied. Generated by cloudfront (CloudFront) or the raw XML AccessDenied response passing through from S3.
Troubleshooting a cloudfront 403 or s3 access denied requires isolating the layer where the rejection occurs. The request lifecycle passes through DNS, CloudFront edge locations, AWS WAF (if attached), and finally the S3 origin. A denial at any of these layers results in a 403 status code.
Step 1: Isolate the Layer Returning the 403
The first step in diagnosing a 403 is identifying who is returning it. Is CloudFront generating the 403, or is S3 returning a 403 which CloudFront is simply caching and forwarding?
Examine the HTTP response headers. You can do this using curl -I https://your-distribution.cloudfront.net.
- If S3 is returning the 403: You will usually see an
x-amz-error-code: AccessDeniedheader, and the response body will often be an XML payload containingAccessDenied. TheServerheader will readAmazonS3. - If CloudFront/WAF is returning the 403: You will see the standard CloudFront error page (HTML) stating
The request could not be satisfied. TheServerheader will readCloudFront.
Common Cause 1: S3 Bucket Policy and OAC/OAI Configurations
If S3 is the culprit, the most common issue is an incorrect S3 Bucket Policy. To securely serve private S3 content through CloudFront, AWS recommends using Origin Access Control (OAC) (the modern replacement for Origin Access Identity or OAI).
When you enable OAC on your CloudFront S3 origin, CloudFront signs the requests it makes to S3. However, S3 will still reject these requests with s3 access denied unless the S3 Bucket Policy explicitly allows the CloudFront distribution to perform s3:GetObject.
A correct bucket policy looks like this:
{
"Version": "2012-10-17",
"Statement": {
"Sid": "AllowCloudFrontServicePrincipalReadOnly",
"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"
}
}
}
}
If you see amazon s3 exception access denied or s3 putobject access denied 403, verify that your actions (like s3:PutObject for uploads or s3:GetObject for reads) are explicitly allowed and that Block Public Access settings aren't inadvertently overriding your intended cross-account or service-principal access.
Common Cause 2: Missing Default Root Object
A highly confusing scenario occurs when users visit the root of your domain (e.g., https://example.com/) and receive a 403, but visiting https://example.com/index.html works perfectly.
Why does this happen? S3 does not automatically serve index.html for root requests unless configured as a static website endpoint (which is not recommended when using CloudFront OAC). When CloudFront requests the root prefix / from S3, S3 looks for an object literally named /. When it doesn't find it, S3 attempts to return a 404 Not Found. However, for security reasons, if the requester (CloudFront) does not have the s3:ListBucket permission, S3 returns a 403 Access Denied to prevent confirming whether an object exists or not.
The Fix: Go to your CloudFront Distribution Settings and set the Default Root Object to index.html. This tells CloudFront to append index.html to any request for the root URL before forwarding it to S3.
Common Cause 3: KMS Encryption Key Permissions
If your S3 bucket uses AWS Key Management Service (SSE-KMS) to encrypt objects, granting s3:GetObject in the bucket policy is not enough. CloudFront must also have permission to decrypt the objects. If it doesn't, S3 will return a 403.
The Fix: You must modify the KMS Key Policy to allow the CloudFront Service Principal to use the key. Add this statement to your KMS Key Policy:
{
"Sid": "AllowCloudFrontServicePrincipalSSE-KMS",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": [
"kms:Decrypt",
"kms:Encrypt",
"kms:GenerateDataKey*"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
}
}
}
Common Cause 4: AWS WAF and Geo-Restrictions
If the response header says Server: CloudFront and you get the standard HTML error page, the request was blocked at the edge.
- Check AWS WAF: If a Web Application Firewall is attached to your distribution, inspect the WAF metrics and sampled requests. A rate-limiting rule, SQLi block, or managed rule group might be triggering the
cloudfront 403 forbidden. - Check Geo-Restrictions: CloudFront has built-in geographic restrictions. Verify under the 'Geographic Restrictions' tab that you haven't accidentally allow-listed or block-listed the wrong countries.
Correlating 403s with Gateway Errors and Timeouts
While troubleshooting cloud architectures, it's vital to distinguish between authorization errors (403s) and execution/connectivity errors (502s, 503s, 504s).
For example, if you are using Lambda@Edge to dynamically modify headers or authorize requests, a poorly optimized function might hit the lambda max timeout aws limit. A lambda task timed out after 3.00 seconds error will not manifest as a 403; instead, CloudFront will return a 503 Service Unavailable or 502 Bad Gateway.
If you see 502 bad gateway microsoft azure application gateway v2 or aks 502 bad gateway in an Azure environment, or gcp cloud function timeout limit in Google Cloud, the root cause is typically a backend application crash, network unreachable event (like ec2 instance suddenly unreachable), or a strict timeout configuration on the gateway or load balancer.
To fix lambda timeout api gateway or amazon lambda timeout issues, you must increase the timeout in the function's configuration (e.g., up to the 15-minute maximum for standard Lambda, or 5-30 seconds for Lambda@Edge depending on the trigger type). However, remember: timeouts and bad gateways are infrastructure flow failures, while 403 access denied s3 is strictly a policy, IAM, or WAF evaluation failure.
Summary of Diagnostic Steps
- Run
curl -vagainst the asset to check theServerheader. - If S3, verify the Bucket Policy explicitly allows
s3:GetObjectfor the CloudFront OAC ARN. - If 403 occurs on the root path, set the Default Root Object in CloudFront.
- If objects are KMS encrypted, verify the KMS Key Policy grants
kms:Decryptto CloudFront. - If blocked at the Edge, review AWS WAF logs and CloudFront Geo-Restriction settings.
Frequently Asked Questions
#!/bin/bash
# Diagnostic script to inspect S3 and CloudFront configurations for 403 errors
BUCKET_NAME="your-target-bucket-name"
DISTRIBUTION_ID="E1XXXXXXXXXX"
echo "=== 1. Checking S3 Public Access Block ==="
aws s3api get-public-access-block --bucket $BUCKET_NAME
echo "\n=== 2. Checking S3 Bucket Policy (Look for CloudFront Service Principal) ==="
aws s3api get-bucket-policy --bucket $BUCKET_NAME --query Policy --output text | jq .
echo "\n=== 3. Checking CloudFront OAC Configuration ==="
aws cloudfront get-distribution --id $DISTRIBUTION_ID \
--query 'Distribution.DistributionConfig.Origins.Items[*].OriginAccessControlId'
echo "\n=== 4. Checking CloudFront Default Root Object ==="
aws cloudfront get-distribution --id $DISTRIBUTION_ID \
--query 'Distribution.DistributionConfig.DefaultRootObject'
echo "\n=== 5. Checking for AWS WAF WebACL ==="
aws cloudfront get-distribution --id $DISTRIBUTION_ID \
--query 'Distribution.DistributionConfig.WebACLId'Error Medic Editorial
Error Medic Editorial comprises senior DevOps and SRE professionals dedicated to untangling complex cloud infrastructure, network, and security gateway challenges.