I deployed a web application that fetches images and documents from an S3 bucket, and the browser console exploded with CORS errors. The requests were failing, users couldn’t see images, and I had no idea why the S3 bucket wasn’t responding with the proper headers. After reading a few AWS docs and testing different configurations, I realized CORS wasn’t enabled on the bucket at all—and when I did enable it, I forgot to include the correct HTTP methods. In this post, I’ll walk through exactly what causes this and how to fix it.
The Problem
Your web application makes a cross-origin request to S3 (e.g., https://myapp.com requests from https://my-bucket.s3.amazonaws.com). The browser blocks the request and logs: “Access to fetch at ‘https://my-bucket.s3.amazonaws.com/file.jpg’ from origin ‘https://myapp.com’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present”.
| Error Type | Description |
|---|---|
| No Access-Control-Allow-Origin Header | S3 didn’t return the CORS header, usually because CORS is not configured. |
| Method Not Allowed in CORS | Request uses PUT or DELETE, but CORS config only allows GET. |
| Preflight Request Failed | Browser’s OPTIONS request failed, blocking the actual request. |
Why Does This Happen?
- No CORS configuration on the bucket — S3 CORS is disabled by default. Requests from different origins are blocked unless you explicitly configure CORS.
- CORS config doesn’t include your requesting origin — The CORS rule specifies
AllowedOriginsas a list. If your origin (https://myapp.com) isn’t in the list, the request is blocked. - CORS doesn’t allow the required HTTP method — Your app uses PUT for presigned uploads, but the CORS config only allows GET. The browser blocks the request before it reaches S3.
- Wildcard in AllowedOrigins without AllowedHeaders — Using
*for origins requires explicitAllowedHeadersto allow all headers, or the request fails.
The Fix
Step 1: Create CORS Configuration
Create a CORS configuration file with the appropriate settings:
cat > cors-config.json <<EOF
{
"CORSRules": [
{
"AllowedOrigins": [
"https://myapp.com",
"https://www.myapp.com"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"DELETE",
"HEAD"
],
"AllowedHeaders": [
"*"
],
"ExposeHeaders": [
"ETag",
"x-amz-version-id"
],
"MaxAgeSeconds": 3000
}
]
}
EOF
Understanding Each Setting
- AllowedOrigins — The origins allowed to make requests. Use exact domains or
*for all (not recommended for production). - AllowedMethods — HTTP methods permitted. Use
GETfor static assets, addPUTandDELETEfor uploads/deletions via presigned URLs. - AllowedHeaders — Request headers the browser can send. Use
*to allow all. - ExposeHeaders — Response headers the browser can read. Always include
ETagfor versioning and upload verification. - MaxAgeSeconds — How long the browser caches the preflight (OPTIONS) response. Default is 0; 3000 seconds (50 minutes) is typical.
Step 2: Apply CORS to the Bucket
Apply the CORS configuration to your S3 bucket:
aws s3api put-bucket-cors \
--bucket my-bucket \
--cors-configuration file://cors-config.json
Step 3: Verify CORS Configuration
Check the configuration was applied:
aws s3api get-bucket-cors --bucket my-bucket
Step 4: Test CORS with a Browser Request
Test CORS by fetching an object from the browser console:
fetch('https://my-bucket.s3.amazonaws.com/test.jpg')
.then(response => response.blob())
.then(blob => console.log('Success:', blob))
.catch(error => console.error('CORS Error:', error));
If CORS is configured correctly, the request succeeds and you see the blob in the console.
Step 5: For Presigned URLs, Ensure Methods Match
If your application uses presigned URLs for uploads, verify the CORS config includes PUT:
# Generate a presigned POST URL (requires PUT in CORS)
aws s3 presign s3://my-bucket/test.jpg \
--expires-in 3600 \
--region us-east-1
How to Run This
- Replace
https://myapp.comwith your actual application domain - Replace
my-bucketwith your bucket name - Include all HTTP methods your app uses (typically GET, PUT for uploads)
- Apply the configuration and test immediately from your browser
- Check the browser’s Network tab to confirm the response includes the
Access-Control-Allow-Originheader
Is This Safe?
CORS with specific allowed origins is safe. Only list the exact domains that need access. Using "AllowedOrigins": ["*"] is generally acceptable for public content but less secure than explicit domains. For sensitive data, always use presigned URLs with short expiry times and specific ACLs.
Key Takeaway
S3 CORS errors occur when the bucket has no CORS configuration or the configuration doesn’t include your application’s origin and required HTTP methods. Configure CORS once, test from the browser, and your cross-origin requests will work seamlessly.
Have questions or ran into a different S3 issue? Connect with me on LinkedIn or X.