Nginx默认仅对200、201、204等成功状态码添加add_header头,4xx/5xx响应中Access-Control-Allow-Origin会丢失;需加always标志并置于add_header末尾(Nginx 1.7.5+),且须确保配置位置覆盖所有路径(如server块顶层或显式处理OPTIONS),避免被error_page重定向或上游服务覆盖。

为什么非 200 响应里 Access-Control-Allow-Origin 没了
Nginx 默认只对 200、201、204、206、301、302、303、304、307 这些状态码自动添加 add_header 设置的响应头。一旦后端返回 401、403、500 等错误码,你配的 Access-Control-Allow-Origin 就直接消失——浏览器看到没 CORS 头,立刻拦截响应,连错误详情都拿不到。
add_header always 的作用和必须加的位置
add_header 指令默认不作用于非成功响应;加上 always 标志后,它才强制在所有状态码(包括 4xx 和 5xx)下生效。但注意:always 必须写在 add_header 的末尾,且只能用于 Nginx 1.7.5+:
add_header Access-Control-Allow-Origin "*" always;
常见错误位置包括:
- 写在
location /api { ... }里但没覆盖全部错误路径(比如静态资源或 fallback location) - 写在
server块顶层却漏掉了error_page重定向后的响应(重定向后响应由新 location 处理,原 header 不继承) - 多个
add_header指令叠加时,后写的会覆盖前写的同名头(Nginx 不合并)
实际配置中要同步补全的关键 CORS 头
光加 Access-Control-Allow-Origin 不够。浏览器预检(OPTIONS)和带凭证请求失败,往往是因为其他头缺失。建议统一用 always 补齐:
add_header Access-Control-Allow-Origin "*" always;<br>add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;<br>add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;<br>add_header Access-Control-Expose-Headers "Content-Length,Content-Range" always;
特别注意:
-
Access-Control-Allow-Credentials: true不能和Access-Control-Allow-Origin: "*"共存,必须指定明确域名 -
OPTIONS请求本身也得能被正确响应,建议显式处理:location = /api/ { if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "..."; add_header Access-Control-Allow-Headers "..."; return 204; } }
验证是否生效的最简方法
别只看浏览器 Network 面板里 200 成功请求的响应头。直接 curl 测试错误响应:
curl -I -H "Origin: https://example.com" http://localhost:8080/api/fail
检查输出里是否有 Access-Control-Allow-Origin。如果没出现,说明:
-
always没加,或者加错位置(比如写在了被error_page覆盖的 location 里) - 后端框架自己设置了同名 header(如 Express 的
res.header()),Nginx 的add_header会被覆盖(Nginx 在应用层 header 之后执行) - 用了
proxy_pass却没配proxy_hide_header,导致上游把 CORS 头删了
复杂点在于:CORS 头的生效依赖完整链路——Nginx 配置、上游服务行为、浏览器缓存、甚至 CDN 是否透传自定义头。最容易被忽略的是 error_page 后的响应完全脱离原始 location 上下文,那里的 add_header always 无效。

















