add_header 默认只在2xx响应中生效,4xx/5xx时跨域头不返回,必须加always参数才能使access-control-allow-origin等头在所有状态码下透出。

直接说结论:add_header 默认只在 2xx 响应中生效,4xx/5xx 时跨域头不会返回,浏览器就仍会报跨域错误;必须加 always 参数才能让 Access-Control-Allow-Origin 等头在所有状态码下透出。
为什么 4xx/5xx 响应里看不到跨域 Header
Nginx 的 add_header 指令默认仅作用于成功响应(即 HTTP 状态码为 2xx),这是设计行为,不是 bug。比如后端返回 401 Unauthorized 或 500 Internal Server Error,即使你配了 add_header Access-Control-Allow-Origin $http_origin,这些头也不会出现在响应里——浏览器收不到声明,就会拦截响应并抛出 CORS 错误,而不是把原始错误信息交给你。
add\_header always 是什么,怎么加
always 是 add_header 的可选参数,告诉 Nginx:不管响应状态码是多少,都强制添加这个响应头。它从 Nginx 1.7.5 开始支持,老版本(如 1.4.3)不识别该参数,会直接报配置错误。
正确写法示例:
add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Access-Control-Allow-Credentials "true" always;
注意点:
-
always必须紧贴在值后面,不能换行或加空格,否则 Nginx 启动失败 - 如果用了
$http_*这类变量,确保它们在非 2xx 响应中仍有值($http_origin通常有,但$http_access_control_request_headers在非预检请求中可能为空) - 不要对同一个 header 写两次(比如一个带
always、一个不带),Nginx 会以最后一个为准,容易误覆盖
OPTIONS 预检请求必须单独处理
即使加了 always,也不能跳过对 OPTIONS 请求的显式响应。因为预检请求本身是独立的 HTTP 请求,若没匹配到 location 或没被 if ($request_method = OPTIONS) 拦截,就可能落到后端,而后端通常不返回跨域头或直接 404,导致预检失败。
推荐做法是显式 return:
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers $http_access_control_request_headers always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Max-Age 3600 always;
return 204;
}
这里用 204 而非 200 更规范(无响应体),且所有跨域头都带 always,确保预检响应本身也满足跨域要求。
容易忽略的兼容性与调试细节
真正上线前要确认几件事:
- 检查 Nginx 版本:
/usr/local/nginx/sbin/nginx -v,低于 1.7.5 就不能用always,得升级或改用其他方案(如后端补头) -
Access-Control-Allow-Origin不能和Access-Control-Allow-Credentials: true共存于*,所以必须用$http_origin动态取值,且确保前端请求带Origin头 - 浏览器开发者工具的 Network 面板里,点开任意一个 4xx/5xx 请求 → Headers → Response,逐行确认那些
Access-Control-*头是否真实存在;光看 2xx 成功请求是不够的 - 如果用
proxy_pass转发,别把add_header错写成proxy_set_header——后者加的是发给后端的请求头,对浏览器完全无效
最常被卡住的地方,其实是忘了 always,或者版本太低却没意识到它不支持,硬调半天发现配置根本没生效。











