CORS best practices
Security and performance best practices for Cross-Origin Resource Sharing (CORS) including origin restrictions, preflight caching, and credential handling.
- Avoid the CORS Wildcard Origin on Private APIs
- Restrict CORS Allowed Methods to Required Operations
- Restrict CORS Allowed Headers to Required Headers
- Cache CORS Preflight Responses with Access-Control-Max-Age
- Handle CORS Preflight OPTIONS Requests Explicitly
- Never Combine CORS Wildcard Origin with Credentials
- Set the Vary: Origin Header When CORS Origin Changes Per Request
- Configure CORS at One Layer Only
- Validate CORS Origins with Exact Matching
Best practices for configuring Cross-Origin Resource Sharing (CORS) to balance security, performance, and functionality in production environments.
Avoid the CORS Wildcard Origin on Private APIs
Cross-Origin Resource Sharing (CORS) permits the wildcard
* for
Access-Control-Allow-Origin, but private APIs should never use it. The wildcard grants every website on the internet access to the API response. An attacker's page can read sensitive data from the API if the user is authenticated through cookies or session tokens.
Set
Access-Control-Allow-Origin to the specific origin that needs access. Maintain an allowlist of permitted origins on the server and validate each incoming
Origin header against it. Return the matched origin in the response header and include
Vary: Origin to prevent cache poisoning.
Restrict CORS Allowed Methods to Required Operations
Cross-Origin Resource Sharing (CORS) should expose only the HTTP methods the client application needs. A front-end that only reads data should receive
Access-Control-Allow-Methods: GET-- not
GET, POST, PUT, DELETE, PATCH. Overly permissive method lists increase the attack surface by letting malicious scripts issue state-changing requests.
Audit the API endpoints each client calls and set
Access-Control-Allow-Methods per route or location block. Separate read-only resources from write endpoints and apply different CORS policies to each.
Restrict CORS Allowed Headers to Required Headers
Cross-Origin Resource Sharing (CORS) should list only the request headers the application sends. Avoid using the
* wildcard for
Access-Control-Allow-Headers when credentials are enabled, because the
Authorization header does not respond to the wildcard in credentialed requests. List each required header explicitly:
Content-Type,
Authorization, and any custom headers the API expects.
Cache CORS Preflight Responses with Access-Control-Max-Age
Cross-Origin Resource Sharing (CORS) preflight requests add latency to every non-simple cross-origin call. The
Access-Control-Max-Age header lets the browser cache the preflight response and skip repeated OPTIONS requests. Set the value to
86400 (24 hours) for stable APIs where the CORS policy does not change frequently.
Different browsers enforce different maximum values. Chrome caps
Access-Control-Max-Age at 7200 seconds (2 hours). Firefox caps it at 86400 seconds (24 hours). Set the header to the highest value your API tolerates and let each browser apply its own limit.
Handle CORS Preflight OPTIONS Requests Explicitly
Cross-Origin Resource Sharing (CORS) preflight requests use the HTTP OPTIONS method. Configure the server to respond to OPTIONS requests with the CORS headers and a
204 No Content status. Do not route OPTIONS requests through application logic, authentication middleware, or rate limiters. A blocked or slow preflight response prevents the actual request from reaching the server.
Return the CORS headers on the OPTIONS response at the web server or reverse proxy level (Nginx, Apache, or a load balancer). This approach avoids unnecessary processing in the application layer.
Never Combine CORS Wildcard Origin with Credentials
Cross-Origin Resource Sharing (CORS) does not allow
Access-Control-Allow-Origin: * when
Access-Control-Allow-Credentials: true. The browser rejects this combination and blocks the response. The server must return an explicit origin when credentials are enabled.
Reflect the validated
Origin request header back in the
Access-Control-Allow-Origin response header after checking it against the allowlist. Include
Vary: Origin to ensure CDNs and intermediate caches serve the correct header for each origin.
Set the Vary: Origin Header When CORS Origin Changes Per Request
Cross-Origin Resource Sharing (CORS) responses that change
Access-Control-Allow-Origin based on the request's
Origin header must include
Vary: Origin. Without this header, a CDN or browser cache may store the response for one origin and serve it to a different origin. The mismatched
Access-Control-Allow-Origin value causes the browser to block the response.
Configure CORS at One Layer Only
Cross-Origin Resource Sharing (CORS) headers set by both the web server (Nginx, Apache) and the application framework (Django, Express, Spring) produce duplicate headers. A response with two
Access-Control-Allow-Origin headers causes the browser to reject the request. Configure CORS at the reverse proxy layer or the application layer -- not both.
If the application sets CORS headers, use
proxy_hide_header Access-Control-Allow-Origin in Nginx to strip the duplicate. If the reverse proxy sets CORS headers, disable the CORS middleware in the application.
Validate CORS Origins with Exact Matching
Cross-Origin Resource Sharing (CORS) origin validation must use exact string matching or a strict regular expression with start and end anchors. A regex like
https://example\.com without a
$ anchor matches
https://example.com.attacker.com, which lets an attacker's domain pass the check. Append
$ to the regex:
^https://example\.com$.
Avoid substring matching, prefix matching, or partial hostname checks when validating origins. Maintain a static allowlist when possible and fall back to anchored regex only for dynamic subdomain patterns.