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.conf or a site-specific file in /etc/nginx/sites-enabled/).

Step-by-Step: Enable CORS on Nginx

  1. Open the Nginx configuration file for the site that needs Cross-Origin Resource Sharing (CORS) headers:

    sudo nano /etc/nginx/sites-enabled/{YOUR_DOMAIN}.conf

    If the site uses the main configuration file:

    sudo nano /etc/nginx/nginx.conf
  2. Add the CORS headers inside the server or location block. The always parameter 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-Origin header 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 map directive-- Use the map directive in the http block to match the Origin header 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;
                }
            }
        }
    }
  3. Test the Nginx configuration for syntax errors:

    sudo nginx -t
  4. Reload 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.com

Test 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. Use proxy_hide_header Access-Control-Allow-Origin; in the Nginx location block to strip the upstream header, or disable CORS middleware in the application.
  • CORS headers missing on error responses.Nginx omits add_header on 4xx and 5xx responses unless the always parameter is included. Add always to every add_header directive.
  • if directive clears headers.Nginx clears all add_header directives from the parent block inside an if block. Repeat the add_header directives inside the if ($request_method = 'OPTIONS') block.
  • Empty Access-Control-Allow-Origin with map.The map directive returns an empty string for unmatched origins. Nginx sends an empty header value, which the browser treats as a CORS failure. Verify the Origin value matches the entries in the map block exactly, including the scheme and port.

For a detailed explanation of the CORS preflight mechanism, see CORS tutorial: How Cross-Origin Resource Sharing works.