How to Configure CORS Headers & Handle OPTIONS Preflight in Nginx
When frontend apps on https://frontend.com call APIs on https://api.com, browsers enforce the Same-Origin Policy. Configuring Nginx to respond to OPTIONS preflight calls and set Access-Control headers solves CORS errors at the edge without touching application code.
Interactive Nginx Config Generator
server {
listen 80;
server_name example.com;
# Enhanced Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
# Handle CORS Preflight OPTIONS requests directly
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' 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;
}
# Regular request headers
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Directives & Architecture Explained
add_header Access-Control-Allow-Origin $http_origin always;Echoes the requesting origin back to the browser while permitting credentials (cookies/auth headers).
return 204;Immediately terminates the preflight OPTIONS request with 204 No Content without passing it to backend runtimes.
Access-Control-Max-Age 86400Caches preflight check result in the user browser for 24 hours to reduce latency on subsequent API calls.
Production Verification & Reload Workflow
sudo nginx -tVerify Nginx syntax before reloading
curl -I -X OPTIONS -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST" http://api.example.com/Send simulated preflight request to verify 204 No Content response and CORS headers
Production Troubleshooting Tips
- β’If you send cookies with fetch/axios (withCredentials: true), Access-Control-Allow-Origin cannot be set to a wildcard *; it MUST match the specific origin.
- β’Always append the always parameter to add_header so Nginx attaches CORS headers even when an upstream returns 4xx or 5xx errors.
- β’Do not set duplicate CORS headers in both Nginx and your Node.js/Express app, as multiple headers will cause browsers to reject the response.
Frequently Asked Questions
Why does the browser send an OPTIONS request before my POST request?
Browsers send preflight OPTIONS requests for any non-simple request (custom headers like Authorization, application/json content type) to verify server permissions before sending payload data.
What does the always parameter do on add_header?
By default, Nginx only adds headers on 200, 201, 204, 206, 301, 302, 303, 304, 307 status codes. Adding always ensures CORS headers appear on 401, 403, 404, and 500 error responses.