正确写法是仅在启用SSL的443端口server块中配置add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload",HTTP server块必须用return 301强制跳转,且所有子域需HTTPS就绪才能提交preload。
nginx 配置 Strict-Transport-Security 头的正确写法
直接加 add_header strict-transport-security "max-age=31536000; includesubdomains; preload" 不够,它在 301 跳转或非 https 响应里不会生效,浏览器直接忽略。
必须确保该 header 只出现在 HTTPS 响应中,且不能被子请求(比如 iframe 加载 HTTP 资源)覆盖。常见错误是把它写在 server 块顶层,但没限制只对 HTTPS 生效。
- 把
add_header放进location /或server块中,但前面加if ($scheme = https) { ... }不推荐——nginx 官方明确说if在 location 外不可靠 - 正确做法:只在启用 SSL 的 server 块里配置,且确保该 server 块监听 443 并有有效证书;不给 HTTP server 块加这个 header
-
max-age=31536000是一年,但首次访问 HTTP 时浏览器收不到,所以必须配合 301 强制跳转
HTTP 到 HTTPS 的 301 跳转必须做全
HSTS 不解决“用户手输 http://”的问题,只解决“已知域名后续访问”的保护。没跳转,第一次访问仍是明文,中间人就能劫持并阻止 HSTS 头下发。
常见漏配:只在 www 域名跳转,主域名没跳;或跳转用的是 302(临时),浏览器不会缓存,下次仍可能走 HTTP。
- HTTP server 块必须返回
return 301 https://$host$request_uri,不是rewrite - 确保
$host不带端口,否则跳转链接可能变成https://example.com:80/,导致失败 - 如果用了 CDN,确认 CDN 没缓存 HTTP 响应或覆盖了跳转逻辑——有些 CDN 默认不透传 301
preload 列表提交前必须验证所有子域 HTTPS 就绪
加了 preload 参数,等于主动申请把自己域名写进 Chrome/Firefox 内置 HSTS 列表。一旦收录,哪怕你之后关掉 HTTPS,用户半年内都打不开你的网站。
不是加个 header 就能提交,hstspreload.org 会严格校验:
- 主域名和所有
includeSubDomains下的子域(如api.example.com、cdn.example.com)都必须可 HTTPS 访问且返回有效证书 - 响应头必须含
Strict-Transport-Security,且max-age≥ 31536000,带includeSubDomains和preload - 必须支持 TLS 1.2+,不能用自签名或过期证书;HTTP 端口必须返回 301(不能是 302 或页面)
curl 和浏览器验证 HSTS 是否生效
别只看响应头有没有,要确认浏览器真记住了。本地开发或测试环境容易误判,因为 localhost 不在 preload 列表,且部分浏览器对无证书 HTTPS 不存 HSTS。
- 用
curl -I https://example.com看是否返回Strict-Transport-Security头,注意检查大小写和空格(必须是首字母大写的完整字段名) - Chrome 中访问
chrome://net-internals/#hsts,输入域名查是否已记录;Firefox 用about:config搜security.cert_pinning.enforcement_level(仅影响 pinning,HSTS 状态需查network.stricttransportsecurity.preloadlist) - 关键验证点:关掉 nginx 的 HSTS header 后,再访问一次 HTTPS,然后清浏览器缓存重进 HTTP 地址——如果仍自动跳 HTTPS,说明 HSTS 已生效并持久化
最容易被忽略的是子域覆盖和 preload 的不可逆性。加了 includeSubDomains 却忘了某个测试子域还跑着 HTTP 服务,上线后那个子域就彻底不可用;提交 preload 后发现 CDN 配置漏了 HTTPS,只能等 6 个月才能撤出列表。











