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