Reverse Proxy & APIscors.conf

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

cors.conf
/etc/nginx/sites-available/cors.conf
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 86400

Caches preflight check result in the user browser for 24 hours to reduce latency on subsequent API calls.

Production Verification & Reload Workflow

sudo nginx -t

Verify 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.