hsts头未出现是因add_header不继承且被内层location覆盖——仅最后一级定义生效,需在所有location中显式声明或用always参数确保发送。

如果你在 Nginx 中配置了 HSTS,但用 curl -I https://example.com 或浏览器开发者工具检查时发现 Strict-Transport-Security 头完全没出现,很可能是 add_header 指令被多层嵌套覆盖或丢弃所致——这不是证书或重定向问题,而是 Nginx 自身机制导致的“静默失效”。
为什么 add_header 在嵌套块中会失效
Nginx 的 add_header 指令不会继承,且同名响应头仅保留最后一级 location / server / http 块中定义的那一条。如果父块(如 server)和子块(如某个 location)都写了 add_header Strict-Transport-Security ...,只有 location 块里的那条生效;若 location 块里没写,而只在更外层写了,那么当请求命中该 location 时,HSTS 头反而会消失。
常见误配场景:
- 在
server块中设置了 HSTS,但又为/api、/static等路径单独写了location块,且未在其中重复声明add_header - 使用了第三方配置片段(如 WordPress、PHP-FPM 通用模板),其内部
location ~ \.php$覆盖了外层 header 设置 - 启用了
proxy_pass后,Nginx 默认不转发原始响应头,而你又没在 proxy location 里显式补上add_header
正确写法:统一收口 + 显式覆盖
推荐将 HSTS 头统一放在 server 块顶层,并确保所有可能响应内容的 location 都显式继承或重新声明,避免依赖继承逻辑:
- 删除所有子
location中对 HSTS 的重复或遗漏配置,改用always参数确保状态码非 2xx 时也发送(例如 301/404 页面):add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - 若必须按路径差异化策略(如 API 不带
includeSubDomains),请在对应location中完整重写整个 header,不要只改部分参数 - 对
proxy_pass类 location,除了add_header,还需确认后端服务本身没返回冲突的 HSTS 头(否则可能被覆盖),必要时用proxy_hide_header Strict-Transport-Security先清空再重加
快速验证是否真丢失
别只看首页,要覆盖典型响应路径测试:
- 主站根路径:
curl -I https://example.com/ - PHP 脚本:
curl -I https://example.com/index.php - 静态资源:
curl -I https://example.com/style.css - 重定向响应:
curl -I http://example.com(应返回 301+Location,不含 HSTS;真正的 HSTS 必须出现在 HTTPS 响应中)
任一路径缺失,就说明该 location 块未正确携带 HSTS 头。
替代方案:用 more\_set_headers(需编译模块)
如果你无法修改所有 location 块,或需要更灵活的 header 控制,可考虑启用 headers-more-nginx-module(OpenResty 默认包含):
- 支持
more_set_headers,具备继承性,且可用变量动态控制 - 示例:
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"; - 注意:需确认 Nginx 编译时已启用该模块(
nginx -V 2>&1 | grep -o with-http-headers-more-module)










