AWS S3 CORS Error — Every Cause and Fix (2026)
Your browser shows 'Access to fetch blocked by CORS policy' when loading from S3 or CloudFront. Here's every cause — missing CORS config, wrong AllowedOrigins, preflight failures — and the exact fix.
Your frontend makes a request to an S3 bucket or CloudFront distribution and the browser throws:
Access to fetch at 'https://mybucket.s3.amazonaws.com/data.json'
from origin 'https://myapp.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Here's every cause and the exact fix.
What CORS Actually Is
CORS (Cross-Origin Resource Sharing) is a browser security feature. When your JavaScript at https://myapp.com tries to fetch from https://mybucket.s3.amazonaws.com, the browser first asks S3: "Are you okay with requests from myapp.com?"
If S3 doesn't respond with the right headers, the browser blocks the request. S3 actually served the file — the browser blocked your JavaScript from reading it.
CORS is enforced by browsers, not servers. Direct curl requests or Postman requests don't have CORS restrictions. If something works in Postman but fails in the browser, it's a CORS issue.
Fix 1: Add CORS Configuration to S3 Bucket
Most common cause. The bucket has no CORS policy at all.
# Check current CORS config
aws s3api get-bucket-cors --bucket your-bucket-name
# Error: The CORS configuration does not existFix — Add CORS policy:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["https://myapp.com"],
"ExposeHeaders": ["ETag", "Content-Length"],
"MaxAgeSeconds": 3600
}
]# Save as cors.json and apply
aws s3api put-bucket-cors \
--bucket your-bucket-name \
--cors-configuration file://cors.jsonFor development, temporarily allow all origins (never in production):
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"],
"AllowedOrigins": ["*"],
"ExposeHeaders": []
}
]Fix 2: Wrong AllowedOrigins — Exact Match Required
Symptom: CORS config exists but still getting errors.
S3 CORS AllowedOrigins requires an exact match of the Origin header the browser sends. Common mistakes:
// BAD — trailing slash breaks matching
"AllowedOrigins": ["https://myapp.com/"]
// BAD — protocol mismatch
"AllowedOrigins": ["http://myapp.com"] // but browser sends https://
// BAD — port missing
"AllowedOrigins": ["https://localhost"] // browser sends https://localhost:3000
// GOOD
"AllowedOrigins": ["https://myapp.com", "https://localhost:3000", "http://localhost:3000"]Check what Origin your browser is actually sending:
# In browser DevTools → Network → click the failing request → Request Headers
# Look for: Origin: https://myapp.comMake sure that exact string is in your AllowedOrigins.
You can use * wildcards only at the start or end:
"AllowedOrigins": ["https://*.myapp.com"] // matches any subdomainFix 3: Preflight OPTIONS Request Failing
For requests with custom headers (like Authorization) or non-GET methods, the browser sends an OPTIONS preflight request first.
Browser sends OPTIONS (preflight):
Origin: https://myapp.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
S3 must respond with:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Fix — Include OPTIONS in AllowedMethods and add required headers:
[
{
"AllowedHeaders": ["Content-Type", "Authorization", "x-amz-*"],
"AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"],
"AllowedOrigins": ["https://myapp.com"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]AllowedHeaders must include all headers your request sends. Use ["*"] to allow all.
Fix 4: CloudFront Not Forwarding CORS Headers
Symptom: S3 CORS config is correct, but getting CORS errors through CloudFront.
CloudFront caches S3 responses. By default, it does NOT forward the Origin header from the browser to S3 — so S3 never sees which origin the request came from and can't send the right CORS headers back.
Fix — Add Origin to CloudFront cache key and forward it to origin:
# Create a cache policy that includes Origin header
aws cloudfront create-cache-policy \
--cache-policy-config '{
"Name": "CORS-S3Origin",
"DefaultTTL": 86400,
"MaxTTL": 31536000,
"MinTTL": 0,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"EnableAcceptEncodingBrotli": true,
"HeadersConfig": {
"HeaderBehavior": "whitelist",
"Headers": {
"Quantity": 2,
"Items": ["Origin", "Access-Control-Request-Headers"]
}
},
"CookiesConfig": {"CookieBehavior": "none"},
"QueryStringsConfig": {"QueryStringBehavior": "none"}
}
}'Or use the AWS managed cache policy CachingOptimizedForUncompressedObjects which already handles CORS correctly, or select "CachingDisabled" for S3 origins that need CORS.
In Console: CloudFront → Distribution → Behaviors → Edit → Cache key and origin requests → Select "Cache policy" → Use "CORS-S3Origin" managed policy.
Fix 5: S3 Static Website vs S3 REST Endpoint
Symptom: CORS works on the REST endpoint but not the website endpoint (or vice versa).
- REST endpoint (
bucket.s3.region.amazonaws.com) — uses S3 bucket CORS config - Website endpoint (
bucket.s3-website-region.amazonaws.com) — serves content differently, can have different CORS behavior
If using CloudFront + S3 static website hosting, the CORS config on the bucket applies to the REST endpoint. For website hosting, ensure CORS is configured and the right endpoint is set as the CloudFront origin.
Fix 6: Presigned URL CORS Issues
Symptom: Regular S3 requests work, but presigned URL requests fail with CORS.
When using presigned URLs for direct browser uploads (PUT to S3), the CORS config must allow PUT from your origin:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": ["https://myapp.com"],
"ExposeHeaders": ["ETag"]
}
]Also, when generating the presigned URL server-side, ensure you're not adding custom headers that aren't in AllowedHeaders.
Debugging CORS
# Test CORS headers directly
curl -v \
-H "Origin: https://myapp.com" \
-H "Access-Control-Request-Method: GET" \
-X OPTIONS \
https://mybucket.s3.amazonaws.com/test.json
# Should see in response:
# Access-Control-Allow-Origin: https://myapp.com
# Access-Control-Allow-Methods: GETIf these headers are missing, the CORS config isn't applied or the Origin doesn't match.
Invalidate CloudFront after changing CORS config:
aws cloudfront create-invalidation \
--distribution-id YOUR_DIST_ID \
--paths "/*"| Symptom | Cause | Fix |
|---|---|---|
| No CORS headers at all | CORS config missing | Add CORS policy to bucket |
| Origin doesn't match | Trailing slash / wrong protocol | Exact match required in AllowedOrigins |
| Preflight fails | OPTIONS not in AllowedMethods | Add OPTIONS/PUT/DELETE to AllowedMethods |
| Works directly, fails via CloudFront | Origin header not forwarded | Add Origin to CloudFront cache key |
| Presigned URL fails | PUT not in AllowedMethods | Add PUT to CORS config |
S3 CORS errors are almost always an AllowedOrigins mismatch or CloudFront not forwarding the Origin header. Check those two first.
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
AWS ALB 504 Gateway Timeout — Every Cause and Fix (2026)
Your ALB returns 504 Gateway Timeout but the app seems fine. Here's every reason this happens — backend timeouts, keepalive mismatches, health check failures — and exactly how to fix each one.
AWS ALB Showing Unhealthy Targets — How to Fix It
Fix AWS Application Load Balancer unhealthy targets. Covers health check misconfigurations, security group issues, target group problems, and EKS-specific ALB controller debugging.
AWS CloudFront 403 Forbidden — Every Cause and Fix (2026)
CloudFront returns 403 Forbidden but your S3 bucket or origin looks fine. Here's every cause — OAC misconfiguration, bucket policy missing, wrong origin domain, geo-restriction — and the exact fix.