CORS

Fix CORS Error in Nginx — Reverse Proxy Configuration

Nginx acts as a reverse proxy in front of your app. Unless you explicitly add CORS headers at the Nginx level, they may be stripped or never sent. This config handles both preflight and regular requests.

Browser Console Error
Access to XMLHttpRequest at 'https://api.example.com' from origin 'https://app.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present.

Add to your server block

server {
    listen 443 ssl http2;
    server_name api.example.com;

    location / {
        # Handle OPTIONS preflight — return immediately without proxying
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin "https://app.example.com";
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "Authorization, Content-Type";
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Max-Age 86400;
            add_header Content-Length 0;
            add_header Content-Type text/plain;
            return 204;
        }

        # Add CORS headers to all other responses
        add_header Access-Control-Allow-Origin "https://app.example.com" always;
        add_header Access-Control-Allow-Credentials "true" always;

        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

The always flag ensures headers appear on error responses too. Without it, a 401 or 500 response will not have CORS headers — the browser will show a CORS error instead of your actual error message, making debugging confusing.

Multiple allowed origins

map $http_origin $cors_origin {
    default                      "";
    "https://app.example.com"    "https://app.example.com";
    "https://staging.example.com" "https://staging.example.com";
}

server {
    location / {
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin $cors_origin;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "Authorization, Content-Type";
            add_header Content-Length 0;
            return 204;
        }

        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Vary Origin always;
        proxy_pass http://localhost:3000;
    }
}

Test and reload

nginx -t          # test configuration — fix errors before reloading
nginx -s reload   # apply changes without dropping connections

After reloading, use CORSFixer to send a real preflight to your API and verify the response headers are correct.

Send a live preflight to your Nginx API →