GuidesPLATFORMS
Configure CORS on S3
Amazon S3 manages cross-origin access through a per-bucket JSON document, which you edit in the console under bucket Permissions or apply directly with the AWS CLI. Buckets placed behind Amazon CloudFront require extra cache behavior rules to forward headers and pass preflight requests. An S3 CORS configuration takes minutes to apply once the rules match how the browser asks.
Where the configuration lives
Cross-origin sharing rules apply per bucket. In the AWS Management Console, open the S3 console, choose General purpose buckets, choose the bucket name, select the Permissions tab, scroll down to Cross-origin resource sharing (CORS), and choose Edit. The console editor accepts a JSON document only.
{
"CORSRules": [
{
"AllowedOrigins": ["https://app.example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
},
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
}The document contains a CORSRules array with one or more rule objects. Each rule supports an optional ID string. AllowedOrigins takes exact origins, a wildcard * for all origins, or a subdomain wildcard like http://*.example.com. AllowedMethods accepts GET, PUT, POST, DELETE, and HEAD. AllowedHeaders and ExposeHeaders take explicit header names or *. MaxAgeSeconds sets how long the browser can cache the preflight answer. Choose Save changes to apply the document.
# Write the rules from a local JSON file
aws s3api put-bucket-cors --bucket my-bucket \
--cors-configuration file://cors.json
# Read them back
aws s3api get-bucket-cors --bucket my-bucket
# Remove the configuration entirely
aws s3api delete-bucket-cors --bucket my-bucketHow S3 evaluates the rules
When S3 receives a preflight request, it evaluates the bucket CORS configuration and applies the first CORSRule that satisfies every matching condition.
- The request
Originheader must match an entry inAllowedOrigins. - The HTTP method named in the
Access-Control-Request-Methodheader must match an entry inAllowedMethods. - Every header listed in the
Access-Control-Request-Headersheader must match an entry inAllowedHeaders.
ACLs and bucket policies continue to apply when CORS is active, so a matching rule never makes a private object publicly readable. The MaxAgeSeconds property tells the browser how many seconds to cache the preflight verdict so repeated requests avoid additional round trips.
CloudFront in front of S3
A default CloudFront cache behavior strips the Origin header and disallows OPTIONS requests, which prevents S3 from returning CORS response headers even when the bucket configuration is valid.
- Allow OPTIONS in the cache behavior allowed HTTP methods.
- Forward the
Originheader to the bucket, along withAccess-Control-Request-MethodandAccess-Control-Request-Headerswhen preflight responses should be cached. Header forwarding is configured with a cache policy and an origin request policy. - The managed origin request policy
CORS-S3Originforwards exactly these headers to S3. - Alternatively, attach the managed response headers policy
SimpleCORSorCORS-With-Preflightto addAccess-Control-Allow-Origindirectly at the CloudFront edge.
# Cache behavior for a distribution in front of the bucket
Allowed HTTP methods: GET, HEAD, OPTIONS
# Origin request policy: forwards the headers S3 needs to evaluate
# CORS. The managed policy CORS-S3Origin covers exactly this:
# Origin
# Access-Control-Request-Method
# Access-Control-Request-Headers
# Response headers policy (optional, attaches the allow-origin header
# at the edge): managed policy SimpleCORS or CORS-With-PreflightVerify with curl
A successful actual request returns an access-control-allow-origin header alongside the object payload. A successful preflight probe returns access-control-allow-origin, access-control-allow-methods, and access-control-max-age. S3 omits all CORS headers whenever the request lacks an Origin header, so curl commands must explicitly provide one.
# Actual request: expect access-control-allow-origin on the response
curl -i -H "Origin: https://app.example.com" \
https://my-bucket.s3.eu-west-1.amazonaws.com/object.json
# Preflight: expect allow-origin, allow-methods and max-age
curl -i -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: PUT" \
https://my-bucket.s3.eu-west-1.amazonaws.com/object.jsonAn HTTP 403 response on the preflight indicates that no CORSRule matched the incoming origin, method, or request headers. A 200 response that lacks access-control-allow-origin indicates that the origin did not match AllowedOrigins, or that an intermediary such as CloudFront dropped the Origin header before it reached S3. The header checker evaluates pasted headers against the browser rules.
Good questions.
Does enabling S3 CORS grant public access to private files?
No. Bucket policies, identity policies, and ACLs continue to apply. A matching CORS rule only lets a browser origin read responses that credentials or public permissions already allow.
Why does S3 return a 403 Forbidden status on an OPTIONS request?
S3 returns 403 on a preflight when the incoming request matches no CORSRule. This happens when the origin is not listed in AllowedOrigins, the requested method is missing from AllowedMethods, or a requested header is not covered by AllowedHeaders.
Why does a curl test against S3 return no CORS headers?
S3 evaluates CORS only when the request includes an Origin header. Omit the header and S3 processes the call as a normal non-CORS request, omitting all access-control headers from the response.
Which AWS CLI commands manage an S3 CORS configuration?
Use aws s3api put-bucket-cors to apply a JSON document, aws s3api get-bucket-cors to read the current document, and aws s3api delete-bucket-cors to remove the configuration from a bucket.