nginx 默认不在4xx/5xx响应中添加cors头,需用add_header ... always强制注入,并置于server块顶层或所有出口location中;同时需显式处理options预检并避免credentials与通配符冲突。

后端返回 4xx 或 5xx 错误时,Nginx 默认不添加 Access-Control-Allow-Origin 等跨域头,浏览器因此拦截响应、连错误详情都拿不到——这不是后端的问题,而是 Nginx 的默认行为限制。解决的关键是用 add_header ... always 强制在所有状态码下注入 CORS 头,并确保配置位置覆盖完整请求生命周期。
必须加 always 参数
Nginx 的 add_header 指令默认只对 200、201、204、301–304、307 等少数成功或重定向状态生效,对 401、403、500 等错误响应完全跳过。加上 always 后,头才会出现在任意响应中(包括错误页、302 跳转、OPTIONS 预检):
add_header Access-Control-Allow-Origin "*" always;add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,Content-Type,Authorization" always;add_header Access-Control-Expose-Headers "Content-Length,Content-Range" always;
注意:always 必须写在指令末尾,且仅支持 Nginx 1.7.5+;若写成 always add_header ... 或位置错乱,将无效。
配置位置要覆盖所有出口路径
CORS 头必须出现在最终返回给浏览器的响应里。常见漏点包括:
- 只配在
location /api/ { }里,但错误响应被error_page 404 = /404.html重定向到静态页——新 location 不继承原头 - 使用
proxy_intercept_errors on;后,302/502 等被内部重定向,原add_header不生效 - 多个
location块叠加,同名头被后定义的覆盖(Nginx 不合并)
推荐做法:把带 always 的 CORS 头统一放在 server 块顶层,或至少确保每个可能生成响应的 location(含 error_page 指向的 location)都显式配置。
单独处理 OPTIONS 预检请求
浏览器对带凭证或自定义头的请求,会先发 OPTIONS。Nginx 若不拦截,就直接转发给后端——而后端往往没实现 OPTIONS 接口,导致预检失败、后续请求根本发不出。
正确做法是显式匹配并立即响应:
- 用
if ($request_method = 'OPTIONS') { ... return 204; } - 或更稳妥地用独立
location = /api/ { }拦截根路径级 OPTIONS - 返回 204 状态,同时带上全部
alwaysCORS 头(含Access-Control-Allow-Methods和Access-Control-Allow-Headers)
避免 Credentials 与通配符冲突
如果前端设置了 credentials: 'include',则:
-
Access-Control-Allow-Origin不能为"*",必须指定明确域名,例如"https://app.example.com" -
Access-Control-Allow-Credentials必须设为"true",且同样加always - 建议配合
add_header Vary "Origin" always;防止 CDN 或代理缓存错误响应
若需多域名支持,可用 map 模块动态匹配 $http_origin,但禁止将未校验的 $http_origin 直接回传(防伪造)。











