Nginx 在 location 添加响应头后,server 层的响应头消失
Nginx 在 location 新增 add_header 后,上层公共响应头消失。区分 always 与继承规则,使用公共片段,或在 1.29.3 及以后局部启用 merge,并检查重复响应头。
在 server 中配置了公共响应头,普通页面能看到它;给某个 location 增加一条 add_header 后,这条路径却只剩下自己的响应头。即使给公共响应头加上 always,它仍然没有回来。
在 Nginx 默认的继承规则下,同一层只要出现任何一条 add_header,就不再继承上一层的整组 add_header。这条规则不区分响应头名称:给图片路径增加 Cache-Control,也可能让上层配置的其他响应头一起消失。
本文针对原生 ngx_http_headers_module。Nginx 1.29.3 起提供 add_header_inherit,可以改变这条规则;没有设置它、使用默认 on,以及不支持该指令的旧版本,仍需按默认继承方式处理。第三方响应头模块和外层 CDN 的改写规则要分别检查。
always 不控制配置继承
下面的 server 片段演示这个问题,放在本机测试 Nginx 的 http 上下文中。先确认回环地址的 8088 端口未被占用,不要直接复制到生产站点:
server {
listen 127.0.0.1:8088;
server_name localhost;
add_header X-Demo-Site "demo-site" always;
location = /plain {
return 200 "plain\n";
}
location = /special {
add_header X-Demo-Route "special" always;
return 200 "special\n";
}
}
按默认规则,/plain 应带有 X-Demo-Site,/special 应带有 X-Demo-Route,但不会继承 X-Demo-Site。两个响应都是 200,所以这里可以排除响应状态码的影响。
always 解决的是另一件事:让已经选定的响应头配置不受通常的状态码范围限制。它从 Nginx 1.7.5 起可用,不能让被继承规则排除的上层配置重新生效。如果 200 响应有头、404 响应没有头,才需要同时检查是否缺少 always;如果同为 200,仅某个配置了本地响应头的路径缺少公共头,应重点检查层级。
找到真正处理响应的配置块
先确认运行版本及实际配置入口。以下命令以 Linux 常见安装路径为例;如果服务使用其他 Nginx 二进制、-c 或 -p 参数,应沿用该服务的实际参数:
nginx -v
sudo nginx -T
-T 从 Nginx 1.9.2 起可用,它会检查并输出配置文件,包括被引入的文件。更旧的版本需要按主配置中的 include 逐个阅读,不能把不支持 -T 当成站点配置错误。输出可能含有内部地址或敏感配置,检查时保留在本机。
围绕缺头的请求路径,检查命中的 server、location 以及嵌套块中全部 add_header。尤其注意引入的公共片段:只要片段在当前层展开了一条 add_header,也会触发相同的继承规则。
如果请求经 error_page、内部改写或命名 location 返回,不能只看最初匹配到的路径配置。需要继续检查最后生成响应的位置。确认该层定义了自己的响应头、上层公共头未被继承后,再选择下面的修改方式。
兼容旧版本:在需要的层级引入公共响应头
可以把已有的公共响应头集中到一个片段中,再在需要它的层级分别引入。以下路径是示例,使用前需创建相应目录和文件,并确保 Nginx 能读取;片段里只放指令,不再包一层 server 或 location。
文件 /etc/nginx/snippets/demo-response-headers.inc:
add_header X-Demo-Site "demo-site" always;
将前面的测试 server 改为:
server {
listen 127.0.0.1:8088;
server_name localhost;
include /etc/nginx/snippets/demo-response-headers.inc;
location = /plain {
return 200 "plain\n";
}
location = /special {
include /etc/nginx/snippets/demo-response-headers.inc;
add_header X-Demo-Route "special" always;
return 200 "special\n";
}
}
/plain 没有自己的 add_header,继续继承 server 的公共头。/special 在本层同时定义公共头和专用头,因此不依赖从 server 继承它们。这样可以集中维护公共头的值,但每个自行定义响应头的层级仍需显式引入。
迁移真实站点时,应保留原有响应头名称、值和适用条件。这里的两个 X-Demo-* 仅用于观察继承,不是一份安全响应头配置清单。也不要因为使用了公共片段,就把只适用于某条路径的缓存规则引入所有页面。
这份示例按默认继承规则编写。如果上层已经启用 add_header_inherit merge,同时重复引入公共片段可能产生重复响应头,需统一选用一种组织方式。
Nginx 1.29.3 及以后:可在局部合并继承
Nginx 1.29.3 于 2025 年 10 月 28 日引入 add_header_inherit。确认实际二进制支持它后,可以只在需要合并的 location 使用 merge:
server {
listen 127.0.0.1:8088;
server_name localhost;
add_header X-Demo-Site "demo-site" always;
location = /plain {
return 200 "plain\n";
}
location = /special {
add_header_inherit merge;
add_header X-Demo-Route "special" always;
return 200 "special\n";
}
}
这时 /special 会保留本层响应头,并追加上一层的响应头配置。它不是“按名称覆盖”:如果上下层都配置同名响应头,不能假设只留下较低层的值。
on 表示使用标准继承模型,不表示无条件继承全部上层响应头;off 取消上一层的继承。继承规则本身还会向下传递,所以把 merge 放在 http 或 server 层,会影响没有再次改写规则的下层配置。只修一条路径时,先把变更限制在对应 location,逐条检查是否出现重复头。
不支持该指令的版本应使用前面的公共片段方案。不要为了修复继承问题,把未经验证的新指令加入旧版本配置并直接重载。
检查配置、重载,再对比同一路径
修改前备份目标配置和公共片段,把备份放在现有 include 通配符不会加载的位置。对正在使用的那套配置先做检查,成功后才重载;下面假设默认二进制和配置入口就是目标实例:
sudo nginx -t && sudo nginx -s reload
配置检查通过只说明语法和引用文件等检查通过,不代表响应头已经符合预期。重载还可能因新配置无法应用而继续使用旧配置,因此要查看重载结果和错误日志。
对于本机示例,用普通 GET 请求查看响应头,丢弃正文:
curl -sS -D - -o /dev/null http://127.0.0.1:8088/plain
curl -sS -D - -o /dev/null http://127.0.0.1:8088/special
修复后,/plain 应有一条 X-Demo-Site,/special 应同时有一条 X-Demo-Site 和一条 X-Demo-Route。需要检查状态码、响应头值及出现次数,不能只搜索到头名称就算通过。
真实站点还应核对原先缺头的那条 URL,以及业务中安全可请求的错误页面,确认预期的 always 行为。存在反向代理或 CDN 时,分别核对源站和公开入口:本机响应已经正确,而公网仍缺头,需要继续定位外层缓存或头部处理,不能反复叠加相同的 Nginx 指令。
若出现重复头或其他路径的响应头发生意外变化,恢复备份配置及片段,重新检查后重载,再核对相同 URL。单纯恢复磁盘文件不会立即改变正在运行的配置。测试结束后移除演示 server 和不再引用的演示片段,检查并重载,避免遗留测试入口。


