Fix CORS on Nginx

Last updated: April 2026

Browser Console Error
Access to fetch at 'https://api.yoursite.com/data' from origin 'https://yourapp.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

Nginx handles CORS by adding response headers and returning a 204 for OPTIONS preflight requests. The key is handling both the preflight and the actual request correctly.

Test your Nginx CORS live โ†’

Basic fix โ€” nginx.conf location block

location /api/ {
    # CORS headers
    add_header Access-Control-Allow-Origin "https://yourapp.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
    add_header Access-Control-Allow-Credentials "true" always;

    # Handle preflight OPTIONS request
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://yourapp.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 1728000;
        add_header Content-Length 0;
        add_header Content-Type text/plain;
        return 204;
    }

    proxy_pass http://backend;
}
โš  The add_header directives inside if blocks override any add_header directives in the enclosing block. Repeat the headers in both places as shown above.

Multiple origins โ€” dynamic origin matching

map $http_origin $cors_origin {
    default "";
    "https://yourapp.com" "https://yourapp.com";
    "http://localhost:5173" "http://localhost:5173";
    "http://localhost:3000" "http://localhost:3000";
}

server {
    location /api/ {
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Access-Control-Allow-Credentials "true" always;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin $cors_origin;
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
            add_header Access-Control-Allow-Headers "Authorization, Content-Type";
            add_header Access-Control-Max-Age 1728000;
            return 204;
        }

        proxy_pass http://backend;
    }
}

Wildcard origin (no credentials)

location /api/ {
    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 "*";
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type";
        return 204;
    }

    proxy_pass http://backend;
}

Verify and reload

# Test config syntax
nginx -t

# Reload without downtime
nginx -s reload

# Test preflight from terminal
curl -X OPTIONS https://api.yoursite.com/data \
  -H "Origin: https://yourapp.com" \
  -H "Access-Control-Request-Method: POST" \
  -v 2>&1 | grep -i "access-control"
📚 HttpFixer Blog โ€” fix guides, explainers, and references โ†’