nginx中302重定向需在触发location内用always显式添加响应头,因external redirect保留当前块头而internal redirect丢弃原头且仅认目标location配置,且302默认不注入add_header。

重定向分两类:external 和 internal,行为完全不同
nginx 中的重定向不是统一机制。你看到的 301/302 响应,可能是 external redirect(浏览器跳转),也可能是 internal redirect(服务端内部跳转,不暴露给客户端)。两者的 add_header 生效逻辑截然不同:
-
external redirect:比如用
return 302 ...或rewrite ... redirect触发。此时响应由当前 location 生成,add_header 只要写在该块里,且带always,就会出现在 302 响应中。 -
internal redirect:比如
try_files $uri @backend、rewrite ... last、error_page 404 = @notfound。这类跳转会丢弃原 location 的所有 add_header,只看目标 location 是否显式配置——哪怕目标是@named_location,也必须自己写全头。
302 响应默认不带安全头,always 是硬性要求
nginx 默认只对部分状态码(如 200、301、304、307、308)注入 add_header。302 被明确排除在外(尤其在 1.12.x 及更早版本)。这意味着:
- 即使你在 server 块写了
add_header Strict-Transport-Security "max-age=31536000" always;,只要 302 响应没落在带always的 location 内,它就不会出现; - 常见错误是把头配在 server 层,却在某个 location 里只写
return 302,结果响应里空空如也; - 正确做法是在触发 302 的 location 块内,直接写带
always的完整头集合,例如:
add_header X-Frame-Options DENY always;
add_header Access-Control-Allow-Origin "*" always;
return 302 https://new.example.com/;
}
后端返回的 302 更容易丢头,需主动拦截补全
当 302 是 upstream(如 Spring Boot、PHP-FPM)直接返回时,nginx 默认仅透传,不会自动添加你配置的 add_header——哪怕你加了 always,也得确保这个指令落在能覆盖该响应路径的 location 中。
- 若用
proxy_pass,且后端返回 302,nginx 不会自动注入头,除非该 location 显式声明了带always的 add_header; - 更稳妥的做法是用
error_page 301 302 = @handle_redirect;拦截,再在@handle_redirect这个 internal location 中手动 return 并加头; - 注意:
@handle_redirect必须声明为internal;,且里面每一条安全头都要带always,不能依赖上级。
Header 丢失的三大高频原因与验证方法
遇到响应里缺头,先别改配置,按顺序排查:
-
是否漏了 always? 对 302、401、403、500 等非标准成功码,不加
always就等于没配; -
是否被 internal 跳转绕过了? 查 access_log 或用
curl -I看最终响应头,再对照配置,确认命中的 location 是否真写了头; -
是否同名头被覆盖? 同一个 location 里两次写
add_header X-Content-Type-Options "nosniff",只有后一条生效;想叠加要用不同 header 名,或借助proxy_pass_header控制上游头。
调试时可加日志变量验证:log_format debug '$sent_http_x_frame_options $sent_http_access_control_allow_origin';,再查 access_log 确认实际发出值。










