如何设置Nginx add_header always确保非2xx状态码下跨域Header始终有效?
- 内容介绍
- 文章标签
- 相关推荐
本文共计911个文字,预计阅读时间需要4分钟。
直接输出结论:
为什么 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,或者版本太低却没意识到它不支持,硬调半天发现配置根本没生效。
本文共计911个文字,预计阅读时间需要4分钟。
直接输出结论:
为什么 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,或者版本太低却没意识到它不支持,硬调半天发现配置根本没生效。

