最稳妥的解法是在 server 块顶层统一配置带 always 的安全头;因 add_header 无继承性,子 location 会完全覆盖父块同名头,internal 重定向和非 2xx/3xx 响应默认不生效,必须显式加 always 才能确保所有响应均携带关键安全头。

直接在 server 块顶层统一配置带 always 的安全头,是最稳妥的解法。多层 location 嵌套时,add_header 不继承、不叠加,只生效于当前块,且子块会完全覆盖父块同名头——这是安全头“神隐”的根本原因。
add_header 不继承,必须显式重写
Nginx 的 add_header 指令没有层级继承机制。你在 server 块里写了安全头,进入 location /api 后,这些头就自动失效;除非该 location 里也明确写出(并带上 always),否则不会保留。
- 错误写法:只在
server块配了add_header Strict-Transport-Security ...,却以为所有子路径都生效 - 正确做法:把关键安全头集中写在
server块,并确保每条都含always - 例外情况:若某
location需要差异化策略(如开放 CORS),就在该块内完整重写全部需用的头,不能只补一条
internal 跳转后原 header 全丢,终点必须自备
用 try_files、rewrite ... last、error_page 或 @namedlocation 触发的 internal 重定向,会彻底抛弃源 location 的所有 add_header。目标 location 若未显式配置,响应中将一个安全头都没有。
- 例如:
location / { try_files $uri @backend; }中的头全无效,location @backend { internal; }必须自己写全add_header ... always; -
error_page 404 = @notfound;后,@notfound块也要独立配头,不能依赖上级 - 代理场景下,后端返回的 302 默认不带任何你配的安全头——除非你用
error_page 301 302 = @handle_redirect;拦截并手动注入
非 2xx/3xx 响应默认不加头,always 是硬性要求
add_header 默认仅对 200、201、204、301、304 等少数状态码生效。401、403、500、302 等响应中,Access-Control-Allow-Origin、Strict-Transport-Security 等关键头全部消失,导致跨域失败、HSTS 不启用、点击劫持防护失效。
- 必须为每条安全头加上
always,语法是add_header Header-Name "value" always;,漏掉分号或拼错always会导致配置加载失败 - 常见必须 always 的头包括:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;add_header X-Frame-Options "DENY" always;add_header X-Content-Type-Options "nosniff" always;add_header Access-Control-Allow-Origin "*" always; - 注意:Nginx 1.7.5+ 才支持
always,当前主流版本均已满足
同名头后写覆盖前写,避免重复定义
同一个作用域内多次使用 add_header 设置同一响应头,只有最后一条生效。这不是合并,而是覆盖。
- 比如在
location /admin里先后写了两次add_header X-Frame-Options,仅后者起作用 - 不要在
http、server、多个location中零散添加同一头,容易遗漏always或相互覆盖 - 建议全站统一放在
server块顶部,一次性写齐、全部带always,再按需在特定location中覆盖(如有特殊需求)

















