必须用 $sent_http_strict_transport_security,因其捕获响应发出前真实下发的 hsts 头值;而 $http_strict_transport_security 读取客户端请求头,该头客户端从不发送,故恒为空,无法用于审计。

直接通过 Nginx 变量 $sent_http_strict_transport_security 审计全站 HSTS 策略,本质是利用 Nginx 在响应发出前已生成的响应头值,将其记录到访问日志中,从而实现对每个请求是否成功下发 HSTS 头、内容是否合规的可观测性。这不是实时拦截或修改策略,而是事后回溯与批量验证的关键手段。
为什么必须用 $sent_http_strict_transport_security 而不是 $http_strict_transport_security
$http_strict_transport_security 读取的是客户端发来的请求头——而 HSTS 是服务器单向下发给浏览器的策略,客户端绝不会携带这个头发起请求。所以该变量始终为空,完全无效。
$sent_http_strict_transport_security 则不同:它由 Nginx 在构造完响应后、实际发送前捕获,真实反映本次响应是否设置了 HSTS 头、值是什么。只有它能用于审计。
在 access_log 中记录并提取 HSTS 响应值
在 Nginx 配置的 http 或 server 块中,定义一个自定义日志格式:
log_format hsts_audit '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$sent_http_strict_transport_security" '
'"$http_user_agent" $request_time;'然后在对应 server 或 location 中启用该格式:
access_log /var/log/nginx/hsts-audit.log hsts_audit;
重启 Nginx 后,所有 HTTPS 响应(且配置了 HSTS 的)都会在日志中留下一行,第三字段即为实际下发的 HSTS 值,例如:
203.0.113.42 - - [30/Apr/2026:14:22:18 +0000] "GET / HTTP/2.0" 200 1245 "max-age=31536000; includeSubDomains; preload" "Mozilla/5.0..." 0.023
用日志快速识别常见 HSTS 问题
对生成的 hsts-audit.log 文件执行简单分析,可立刻发现隐患:
-
空值或缺失:某行第三字段为空(如
""),说明该路径未配置 HSTS,可能遗漏了静态资源、API 接口或重定向终点 -
max-age 过短:出现
max-age=300或max-age=600,说明仍在测试阶段未切正式值;若长期存在,无法获得预加载资格,也削弱防护效果 -
缺少 includeSubDomains:值为
max-age=31536000但无includeSubDomains,意味着子域名(如api.example.com)不受保护,易成攻击入口 -
preload 存在但未提交:日志中含
preload,但域名未在 hstspreload.org 提交审核,属于高风险误配——一旦生效,撤回需数月
配合其他检查确保 HSTS 生效闭环
仅靠日志还不够,需交叉验证:
- 确认 HSTS 头只出现在 HTTPS 响应中:HTTP(80端口)响应里
$sent_http_strict_transport_security必然为空,否则说明配置错误地写进了非 SSL server 块 - 检查响应状态码:HSTS 仅被浏览器接受当且仅当响应状态码是 2xx(如 200、204)。若日志中大量 301/302 响应携带 HSTS 头,属于浪费且无效(浏览器忽略)
- 排查反向代理干扰:若前端有 CDN 或 WAF,需确认它们未移除或覆盖该头;可在 curl 中加
-I直连 Nginx 后端比对,排除中间层影响










