Nginx的add_header是覆盖式而非继承式,子location中任意add_header会清空父级所有头;同名头后写覆盖前写;多值需合并为一条;4xx/5xx及302需加always才生效;internal重定向后原location头全丢失,须在终点location显式重配。

直接在 server 或 http 块里配了安全头,结果访问 /api 或 /admin 时发现 X-Frame-Options、Strict-Transport-Security 全没了——不是配置没生效,而是被 location 块里的 add_header 彻底清空了。
子 location 一写 add_header,父级所有头全失效
Nginx 的 add_header 是覆盖式声明,不是继承式叠加。只要某个 location 块里出现任意一条 add_header 指令,该 location 下的响应就只包含它自己定义的头,server 或 http 块里配的同名或不同名头一律不带。
- server 块写了 add_header X-Content-Type-Options nosniff; 和 add_header X-Frame-Options DENY;
- location /api { add_header Cache-Control "no-store"; }
- 访问 /api 时,X-Content-Type-Options 和 X-Frame-Options 都不会出现在响应中
同一 location 内后写的同名头会覆盖前面的
一个 location 里多次用 add_header 设置同一个字段,Nginx 按配置顺序取最后一条生效,前面的会被丢弃。
- location / { add_header X-Powered-By "nginx"; add_header X-Powered-By "custom"; }
- 实际响应头里只有 X-Powered-By: custom
- 想同时发多个值(如多个 CORS 方法),不能靠多条 add_header,得写成一条:add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
4xx/5xx 和 302 响应默认不加头,必须加 always
add_header 默认只对 2xx、3xx 中的部分状态码生效(比如 302 在老版本中就不在默认列表里)。这意味着:
- 后端返回 401,Access-Control-Allow-Origin 不会出现,浏览器直接拦截跨域错误响应
- 用户登录失败跳转到 /login?error=1(302),Strict-Transport-Security 缺失,HSTS 不启用
- 500 页面没有 X-Content-Type-Options,可能触发 MIME 类型嗅探风险
- 解决方法:所有关键安全头都补上 always,例如:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
internal 跳转后原 location 的头彻底消失
用 try_files、rewrite last、error_page 或 @namedlocation 触发的 internal 重定向,响应完全由目标 location 处理,源 location 的 add_header 一条都不会保留。
- location / { try_files $uri @backend; }
- location @backend { internal; add_header X-Source "upstream"; }
- 最终响应只有 X-Source: upstream,/ 块里配的所有头全丢
- 必须在每个终点 location(包括 @named、error_page 指向的 location)里显式重写全部需要的头










