Configuring CORS in Nginx allows a browser application on one origin to call an API on another origin without the browser discarding the response. Tight rules also keep a public site from reading responses that were never meant to be shared cross-origin.

Browsers evaluate CORS with HTTP headers rather than a server-side switch. When a request includes an Origin header, Nginx can return headers such as Access-Control-Allow-Origin, and requests that use non-simple methods, JSON payloads, or custom headers commonly trigger a preflight OPTIONS request before the real request is sent.

Use an explicit origin when the response should be shared with one application rather than every site, add Vary: Origin so caches keep origin-specific responses separate, and return Access-Control-Allow-Credentials: true only when the browser must send cookies or HTTP authentication. Examples below use the common Linux layout with /etc/nginx/sites-available/default and the nginx systemd unit, but the same directives work in other packaged layouts.

Steps to configure CORS headers in Nginx:

  1. Decide the exact allowed origin, methods, headers, and whether the browser must send credentials.

    The origin must match scheme, host, and port, such as https://app.example.com or http://localhost:3000. If the browser must send cookies or HTTP authentication, use an explicit origin instead of *.

  2. Open the target Nginx virtual host or API configuration file in a text editor.
    $ sudoedit /etc/nginx/sites-available/default

    Common configuration locations include /etc/nginx/sites-available, /etc/nginx/conf.d/, and files included from /etc/nginx/nginx.conf depending on the distribution.

  3. Add the CORS headers in the API location block and return a no-content response for preflight requests.
    location /api/ {
        add_header Access-Control-Allow-Origin "https://app.example.com" 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 "86400" always;
        add_header Vary "Origin" always;
    
        if ($request_method = OPTIONS) {
            return 204;
        }
    
        proxy_pass http://127.0.0.1:9000;
    }

    Add add_header Access-Control-Allow-Credentials "true" always; only when the browser must send cookies or HTTP authentication. Browsers reject that header when Access-Control-Allow-Origin is *.

    If a nested location or an if in location block adds its own add_header directives, Nginx stops inheriting the outer header set unless add_header_inherit merge; is enabled in Nginx 1.29.3 or newer. Repeat the full CORS header set in that nested block when needed.

  4. Test the Nginx configuration for syntax errors.
    $ sudo nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
  5. Reload Nginx to apply the changes.
    $ sudo systemctl reload nginx

    On non-systemd hosts, use sudo nginx -s reload to signal the running master process.

  6. Verify the preflight response returns the expected CORS headers.
    $ curl -sSI -X OPTIONS \
      -H 'Origin: https://app.example.com' \
      -H 'Access-Control-Request-Method: POST' \
      -H 'Access-Control-Request-Headers: Content-Type' \
      http://127.0.0.1/api/
    HTTP/1.1 204 No Content
    Server: nginx/1.24.0 (Ubuntu)
    Access-Control-Allow-Origin: https://app.example.com
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Access-Control-Allow-Headers: Content-Type
    Access-Control-Max-Age: 86400
    Vary: Origin

    If the browser sends additional request headers such as Authorization, add those names to Access-Control-Allow-Headers or the preflight fails.

  7. Verify a normal request still returns the same origin-specific CORS headers.
    $ curl -si -H 'Origin: https://app.example.com' http://127.0.0.1/api/
    HTTP/1.1 200 OK
    Server: nginx/1.24.0 (Ubuntu)
    Content-Type: application/json
    Content-Length: 15
    Access-Control-Allow-Origin: https://app.example.com
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Access-Control-Allow-Headers: Content-Type
    Access-Control-Max-Age: 86400
    Vary: Origin
    
    {"status":"ok"}

    When the frontend must send cookies or HTTP authentication, add Access-Control-Allow-Credentials: true and keep Access-Control-Allow-Origin set to one explicit origin instead of *.