Nginx配置CORS最容易失败的三点是:add_header未加always导致OPTIONS响应缺失头、if与proxy_pass混用引发502或header丢失、Access-Control-Allow-Origin:*与credentials:true共存;正确做法是统一加always、拆分location处理预检、动态设置具体origin,或优先采用反向代理规避CORS。

直接给结论:Nginx 配置 CORS 最容易失败的点不是写错头,而是 add_header 没加 always、if 和 proxy_pass 混用、以及 Access-Control-Allow-Origin: * 和 Access-Control-Allow-Credentials: true 同时出现——这三处任一出错,前端都会卡在 OPTIONS 预检,控制台报 “CORS header ‘Access-Control-Allow-Origin’ missing” 或 “The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’”。
add_header 必须带 always 参数才对 OPTIONS 生效
浏览器对 POST/PUT/带 Authorization 头的请求,会先发一次 OPTIONS 预检。Nginx 默认只在 2xx/3xx 响应里注入 add_header,而 return 204 是无 body 的成功响应,不触发默认注入逻辑——结果就是预检响应里压根没 CORS 头,前端直接报错。
正确写法是所有 add_header 后都显式加 always:
add_header 'Access-Control-Allow-Origin' 'https://fe.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always; add_header 'Access-Control-Max-Age' 1728000 always;
验证方式:curl -I -X OPTIONS http://your-api.com/api/,确认响应头里真有这些字段。
不要在同一个 location 里混用 if 和 proxy_pass
这种写法会导致 502、header 丢失、后端收不到 Host 或 X-Real-IP:
location /api/ {
if ($request_method = 'OPTIONS') {
add_header ... always;
return 204;
}
proxy_pass http://backend;
}Nginx 不允许在 if 块内安全使用 proxy_pass,且部分版本会跳过 proxy_set_header。
正确做法是拆成两个有序 location:
- 第一个专用于拦截预检:
location ~ ^/api/.*$ { if ($request_method = 'OPTIONS') { ... return 204; } } - 第二个负责转发真实请求:
location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; } - 注意顺序:预检
location必须写在转发location之前,否则 Nginx 可能按字典序匹配,把 OPTIONS 转发给后端
credentials 为 true 时不能用 * 作为 origin
只要前端 JS 设置了 fetch(..., { credentials: 'include' }) 或 XMLHttpRequest.withCredentials = true,Nginx 就不能设 Access-Control-Allow-Origin: *,否则浏览器直接拒绝响应。
必须写成具体域名,且要动态适配多个来源(比如本地开发 http://localhost:3000 + 测试环境 https://test-fe.example.com):
map $http_origin $cors_origin {
default "";
"http://localhost:3000" "http://localhost:3000";
"https://test-fe.example.com" "https://test-fe.example.com";
"https://fe.example.com" "https://fe.example.com";
}
# 然后在 location 中:
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;漏掉 Access-Control-Allow-Credentials: true 也会导致 cookie 不发送,前后端登录态断开。
反向代理比纯 CORS 更可靠,尤其对本地开发
与其反复调试 add_header 和 if,不如让前后端“物理同源”:前端访问 https://fe.example.com,Nginx 把 /api/ 路径反向代理到后端服务(如 http://127.0.0.1:8080/api/),浏览器认为全是同一源,根本不会触发 CORS。
这个方案绕开了所有预检、origin 白名单、credentials 冲突等问题,上线和本地联调都更稳定。唯一要注意的是 proxy_pass 末尾斜杠的含义:proxy_pass http://backend/ 会剥离 /api/ 前缀;proxy_pass http://backend(无斜杠)则保留前缀。
真正难的不是写几行配置,而是判断该用 CORS 还是代理——如果后端 API 本身不对外暴露,只供自家前端调用,优先选代理;如果 API 是开放给第三方调用的公共服务,才需要认真配 CORS 白名单和预检逻辑。


















