304响应默认不带add_header定义的安全头,因Nginx的add_header指令仅在有响应体的成功状态码下生效,而304无响应体,故跳过注入;需显式添加always参数并在各location中重复配置,且需规避缓存、代理及隐藏头等干扰。

为什么 304 响应默认不带 add_header 定义的安全头
因为 Nginx 的 add_header 指令默认只在“有响应体”的成功状态码下注入头字段,而 304 是纯协商确认响应,无响应体、无内容,Nginx 会跳过所有 add_header 执行逻辑——哪怕文档里写了 304 在支持列表中,实际行为仍是“不生效”。这意味着 X-Frame-Options、Strict-Transport-Security、X-Content-Type-Options 这类关键安全头,在 304 响应中完全缺失,浏览器可能按缺省策略处理,带来点击劫持或 MIME 嗅探风险。
必须加 always 参数,且不能只写一次
always 是唯一绕过状态码限制的方式,它强制 Nginx 在所有响应(包括 304、404、500)中插入头字段。但要注意:
-
always必须写在值之后、分号之前,例如:add_header X-Frame-Options "DENY" always; - 多个头需各自带
always,不能共用:add_header A "1" always; add_header B "2" always;✔️,add_header A "1"; add_header B "2" always;❌(前者不生效) - 不能只在
http或server块写一次就指望全局生效——Nginx 不继承已定义的add_header;若存在location、rewrite last或try_files导致内部重定向,目标location必须也显式声明带always的add_header
缓存场景下,always 只是起点,还要防被过滤
即使加了 always,若启用了 proxy_cache 或 fastcgi_cache,仍可能失效:
- 缓存命中时,Nginx 直接返回原始缓存响应头,不会重新执行
add_header—— 所以缓存里本来没这些头,回传时也不会补上 -
proxy_ignore_headers若误删关键头(如写成proxy_ignore_headers Strict-Transport-Security;),会直接屏蔽always注入的结果 - CDN 或前置反向代理可能默认不缓存或不透传自定义头,需手动开启「自定义响应头缓存」并把
Access-Control-Allow-Origin、X-Frame-Options等加入白名单 - 跨源场景下,若用
Access-Control-Allow-Origin: *,记得配add_header Vary "Origin" always;,否则 CDN 可能复用错误缓存
验证 304 响应是否真带了头,别只看 200
用 curl 模拟协商请求,比浏览器开发者工具更可控:
curl -I -H 'If-None-Match: "abc123"' https://example.com/style.css
观察输出中是否包含你配置的头。若缺失,优先排查:
- 对应
location是否漏了always - 是否被
proxy_hide_header或fastcgi_hide_header显式过滤(例如proxy_hide_header X-Frame-Options;会彻底干掉它) - 是否后端本身返回了同名头,而 Nginx 默认不覆盖(此时必须用
always才能强制覆盖) - 是否模块未启用(确认
ngx_http_headers_module已编译,通常默认开启)
最易忽略的是:内部重定向路径的 location 没加 always,或者缓存配置悄悄屏蔽了头字段——这两处问题不会报错,只会静默丢头。










