302响应默认不携带add_header设置的头,除非显式加always参数;因为add_header默认仅在主响应阶段生效,而302跳转绕过该阶段,必须在同location中用always强制注入所有状态码响应头。

302 响应默认不携带 add_header 设置的头,除非显式加 always 参数 —— 这是 Nginx 的设计行为,不是配置遗漏或 bug。
为什么 302 重定向响应里安全头会消失
Nginx 的 add_header 指令默认只在“主响应生成阶段”注入头字段,而 302 是由 return、rewrite ... redirect 或后端返回触发的跳转响应,它绕过了常规的 location 处理链。此时若没加 always,Nginx 就不会强制插入头。
常见错误现象包括:
- 浏览器收到 302,但
X-Frame-Options、Strict-Transport-Security等头完全缺失 - 用
curl -I https://example.com/old查看响应头,确认状态码是 302 后发现安全头不在其中 - 前端发起跨域请求后被重定向,CORS 头(如
Access-Control-Allow-Origin)没随 302 一起返回,导致后续请求被浏览器拦截
必须在 return / rewrite 所在 location 中加 always
always 不是可选修饰词,而是让 Nginx 在所有响应类型(包括 301/302/307/308/4xx/5xx)中都执行头注入的唯一方式。它必须和 return 或 rewrite ... redirect 出现在同一个 location 块内。
正确写法示例:
location /old-path {
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Access-Control-Allow-Origin "$http_origin" always;
add_header Access-Control-Allow-Credentials "true" always;
return 302 https://example.com/new-path;
}
关键点:
- 不能只在
server块写一次add_header ... always就指望它覆盖所有子location——add_header不继承 - 如果用了
rewrite ^/old(.*)$ /new$1 redirect;,同样要确保该location内有带always的add_header - 多个同名
add_header会覆盖,例如写了两次add_header X-Frame-Options,只有最后一个生效
代理场景下 302 来自后端时的处理要点
当 302 是后端应用(如 Spring Boot、Node.js)返回的,Nginx 默认只是透传,不会自动补安全头 —— 即使你配了 add_header ... always,也得看它是否落在能生效的位置。
推荐做法:
- 用
error_page 301 302 = @handle_redirect;拦截后端返回的 302,再在@handle_redirect中手动return并加头 - 确保
@handle_redirect是internal,避免外部直接访问 - 用
$sent_http_location提取原始 Location 值,再通过return 302 $sent_http_location发出新跳转 - 务必在
@handle_redirect内重复声明所有需要的add_header ... always,不要依赖父块
示例片段:
location /api/ {
proxy_pass http://backend;
error_page 301 302 = @handle_302;
}
location @handle_302 {
internal;
proxy_hide_header Location;
add_header X-Frame-Options DENY always;
add_header Access-Control-Allow-Origin "$http_origin" always;
return 302 $sent_http_location;
}
容易被忽略的兼容性与验证细节
加了 always 不代表万事大吉。实际部署中这几个点最容易翻车:
-
proxy_hide_header或fastcgi_hide_header会直接屏蔽你加的头,检查是否误写了proxy_hide_header X-Frame-Options; - CDN 或前置网关(如 Cloudflare)可能过滤或不缓存自定义响应头,需确认其支持透传
X-Frame-Options等字段 - 测试必须用真实 302 流程:先发一次请求拿到
Location,再带相同Accept/Origin头重发,并用curl -I -H "Origin: https://test.com"验证 -
308和307同样适用always规则,但部分旧客户端支持度低,生产环境优先用302或301
真正生效的标志不是看 200 响应有没有头,而是确认 302 响应体为空、状态码为 302、且所有关键安全头都在响应头里 —— 少一个,就可能让整个防护链条失效。











