How to configure CORS on Nginx
Enable Cross-Origin Resource Sharing (CORS) on Nginx using the add_header directive with preflight OPTIONS handling in the server or location block.
Configure Cross-Origin Resource Sharing (CORS) headers on Nginx to allow cross-origin requests and handle preflight OPTIONS requests at the reverse proxy level.
Prerequisites
- Nginx installed and running.
- Root or sudo access to the server.
- Access to the Nginx configuration file (
/etc/nginx/nginx.confor a site-specific file in/etc/nginx/sites-enabled/).
Step-by-Step: Enable CORS on Nginx
Open the Nginx configuration file for the site that needs Cross-Origin Resource Sharing (CORS) headers:
sudo nano /etc/nginx/sites-enabled/{YOUR_DOMAIN}.confIf the site uses the main configuration file:
sudo nano /etc/nginx/nginx.confAdd the CORS headers inside the
serverorlocationblock. Thealwaysparameter ensures Nginx sends CORS headers on error responses (4xx and 5xx), not only on successful responses.Option A: Allow a specific origin-- Set the
Access-Control-Allow-Originheader to the exact origin that needs access:server { listen 443 ssl; server_name api.example.com; location / { add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; add_header 'Access-Control-Max-Age' '86400' always; add_header 'Vary' 'Origin' always; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; add_header 'Access-Control-Max-Age' '86400' always; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' '0'; return 204; } proxy_pass http://backend; } }Option B: Allow all origins-- Replace the specific origin with
*for public APIs that do not use credentials:location / { add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type' always; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type' always; add_header 'Access-Control-Max-Age' '1728000'; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' '0'; return 204; } }Option C: Allow multiple origins with the
mapdirective-- Use themapdirective in thehttpblock to match theOriginheader against a list of permitted origins:http { map $http_origin $cors_origin { default ""; "https://app.example.com" "$http_origin"; "https://admin.example.com" "$http_origin"; } server { location / { add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; add_header 'Vary' 'Origin' always; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; add_header 'Access-Control-Max-Age' '86400' always; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' '0'; return 204; } } } }Test the Nginx configuration for syntax errors:
sudo nginx -tReload Nginx to apply the CORS configuration:
sudo systemctl reload nginx
How to Verify CORS Is Working on Nginx
Send a test request with
curl to confirm Nginx returns the Cross-Origin Resource Sharing (CORS) headers:
curl -I -H "Origin: https://app.example.com" https://api.example.com/The response should include:
Access-Control-Allow-Origin: https://app.example.comTest the preflight response:
curl -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: Content-Type, Authorization" \
-i https://api.example.com/Common Issues When Configuring CORS on Nginx
- Duplicate CORS headers.Both Nginx and the upstream application set
Access-Control-Allow-Origin. The browser rejects the response. Useproxy_hide_header Access-Control-Allow-Origin;in the Nginxlocationblock to strip the upstream header, or disable CORS middleware in the application. - CORS headers missing on error responses.Nginx omits
add_headeron 4xx and 5xx responses unless thealwaysparameter is included. Addalwaysto everyadd_headerdirective. ifdirective clears headers.Nginx clears alladd_headerdirectives from the parent block inside anifblock. Repeat theadd_headerdirectives inside theif ($request_method = 'OPTIONS')block.- Empty
Access-Control-Allow-Originwithmap.Themapdirective returns an empty string for unmatched origins. Nginx sends an empty header value, which the browser treats as a CORS failure. Verify theOriginvalue matches the entries in themapblock exactly, including the scheme and port.
For a detailed explanation of the CORS preflight mechanism, see CORS tutorial: How Cross-Origin Resource Sharing works.