协议特性
HSTS 详解:严格传输安全、预加载与常见坑
HSTS(HTTP Strict Transport Security,HTTP 严格传输安全)是一个响应头,告诉浏览器在指定秒数内只能用 HTTPS 访问这个主机。浏览器一旦收到 Strict-Transport-Security: max-age=31536000,之后所有 http:// 请求都会在发出之前被它自己改写成 https://,而且遇到证书错误不再允许用户点“继续访问”。它补上了 301 跳转留下的明文缺口,代价是证书出错会直接变成网站打不开。
HSTS 解决什么问题
HSTS 去掉的是“第一跳明文请求”。没有 HSTS 时,用户在地址栏输入 example.com,或者点了一个旧的 http:// 链接,第一个请求是明文发出去的。同一路径上的攻击者(公共 Wi-Fi、被控制的路由器)可以抢先应答,让用户一直停在 HTTP 上,自己在后面代理真正的 HTTPS 站点。这就是促成 RFC 6797 的 SSL stripping(SSL 剥离)攻击。
没有 HSTS 有 HSTS(首次访问之后)
-------- ----------------------
输入 example.com 输入 example.com
| |
v v
GET http://example.com/(明文) 浏览器内部直接改写:
| <-- 攻击者可在这里截获 https://example.com/(内部 307)
v |
301 -> https://example.com/ v
| TLS 握手,校验证书
v (证书错误不可绕过)
TLS 握手
301 跳转仍然需要,首次访问的用户靠它进入 HTTPS;HSTS 负责让之后的每次访问都跳过 HTTP。
响应头和三个指令
这个头只有一个必填指令、两个可选指令,并且只在证书有效的 HTTPS 连接上被浏览器接受。
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
| 指令 | 出处 | 含义 |
|---|---|---|
max-age=<秒> |
RFC 6797 | 必填。浏览器记住策略多久,每次收到新响应都会重新计时。max-age=0 表示删除策略。 |
includeSubDomains |
RFC 6797 | 策略覆盖所有层级的子域名:www、api、intranet.corp 等等。 |
preload |
不在 RFC 6797 里 | 只是给 hstspreload.org 看的“同意收录”标记,浏览器本身不把它当指令处理。 |
RFC 6797 里几条容易踩坑的规定:
- HTTP 响应里的头被忽略。 防止攻击者在明文里设置或清除你的策略。
- 证书必须有效。 如果这次 HTTPS 响应本身证书有问题,头不会被记录。
- 策略按主机生效。
www.example.com发的头管不到example.com。想用includeSubDomains覆盖整个域名,根域名自己必须发这个头,所以预加载检查的是根域名。 - 错误变成硬失败。 RFC 6797 第 12.1 节称之为“no user recourse”(用户无路可退)。HSTS 主机证书过期、域名不匹配、颁发者未知,浏览器都只显示错误页,没有“继续前往”的链接。
首次访问问题与预加载列表
HSTS 保护不了从没见过你这个头的浏览器,预加载列表就是为了补这个洞。Chrome 在源代码里内置了一份 HSTS 主机列表,Firefox、Safari、Edge 的列表也以它为基础。在列表里的域名,浏览器从第一次请求起就只走 HTTPS。
截至 2026 年 11 月,通过 hstspreload.org 提交需要满足:
- 证书有效。
- 如果监听 80 端口,必须在同一主机上把 HTTP 跳转到 HTTPS。
- 所有子域名都支持 HTTPS;只要
www有 DNS 记录,www也必须支持。不对外开放的内网子域名同样算在内。 - 根域名的 HTTPS 响应带 HSTS 头:
max-age至少31536000秒,包含includeSubDomains和preload。 - HTTPS 站点上如果还有额外的跳转,跳转响应本身也要带 HSTS 头。
官网的说法很直白:移除“通常又慢又痛苦”,新条目进入 Chrome 稳定版“可能要几个月”,其他浏览器跟不跟进也没法保证。现在的页面还明确写着:推荐 HSTS,但不推荐 HSTS 预加载。原因是 Chrome 和 Safari 已经会自动把 HTTP 导航升级为 HTTPS,预加载只在攻击者主动阻断这种升级时才有额外价值。
整个顶级域名也可以预加载。Google 就把 .dev、.app 等自己运营的后缀整体加进了列表,这些后缀下的任何域名在浏览器里天生只能走 HTTPS。买了 .app 域名,第一天就得准备好证书,没有 HTTP 可退。
国内基于 Chromium 内核的浏览器(360、QQ 浏览器等)一般也带有预加载列表,但内核版本可能落后于 Chrome,新加入或刚移除的条目生效时间会更晚。
分阶段安全上线
max-age 要分阶段逐步拉长,每个阶段等满时长再进入下一阶段。hstspreload.org 推荐的就是这个做法,并且从一开始就带上 includeSubDomains,让子域名的问题尽早暴露。
| 阶段 | 头的取值 | 进入下一阶段前等待 | 检查什么 |
|---|---|---|---|
| 1 | max-age=300; includeSubDomains |
5 分钟到 1 天 | 每个子域名都能 HTTPS 打开,没有混合内容 |
| 2 | max-age=604800; includeSubDomains |
1 周 | 内部工具、测试环境、网页邮箱、老的活动页 |
| 3 | max-age=2592000; includeSubDomains |
1 个月 | 流量和错误率没有异常 |
| 4 | max-age=31536000; includeSubDomains |
长期 | 证书监控和自动续期已就位 |
| 5(可选) | max-age=63072000; includeSubDomains; preload |
然后再去提交 | 确认今后没有任何子域名需要 HTTP |
一定要等满,因为浏览器一旦记住了较长的 max-age,你之后改短也没用,它要等下次访问才会拿到新值。
开始之前先把证书续期自动化。开了 HSTS 以后,漏续一次证书不是“用户可以点过去的警告”,而是事故。参见自动化证书续期,以及47 天证书里证书有效期不断缩短的时间表。
服务器配置示例
HSTS 头要加在所有 HTTPS 响应上,包括错误页和跳转。nginx 里要带 always 参数,否则 4xx、5xx 响应不会加这个头。
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri; # 这里不加 HSTS,加了也会被忽略
}
server {
listen 443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
root /var/www/example.com;
}
}
nginx 有个细节导致很多“HSTS 头莫名消失”的问题:只有当前层级没有任何 add_header 时,才会继承上一层的 add_header。如果某个 location 里用 add_header 加了 Cache-Control,这个 location 的 HSTS 头就悄悄没了。解决办法是在该 location 里重复写一遍 HSTS,或者把公共响应头放进 include 文件。
Apache(需启用 mod_headers):
<VirtualHost *:443>
ServerName example.com
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>
宝塔面板:SSL 页面的“强制 HTTPS”开关只是加了 301 跳转,并不等于 HSTS。需要在“网站 → 设置 → 配置文件”里,于 443 的 server 块中手动加上面那行 add_header,保存后宝塔会自动重载 nginx。
阿里云、腾讯云等 CDN:TLS 在 CDN 节点终止时,HSTS 应该在 CDN 控制台的 HTTPS 配置里开启,这类控制台通常都有 HSTS 开关。如果 CDN 和源站都加了这个头,响应里会出现两个;RFC 6797 规定浏览器只处理第一个,所以要么两边取值一致,要么只在一处设置。
命令行验证:
curl -sI https://example.com/ | grep -i strict-transport-security
curl -sI https://example.com/does-not-exist | grep -i strict-transport-security # 404 也要有
strict-transport-security: max-age=31536000; includeSubDomains
HSTS 检测工具会做同样的检查,并告诉你域名是否已在预加载列表中。
常见坑
HSTS 事故大多出在没人记得的子域名上,而不是主站。
- 被遗忘的纯 HTTP 子域名。
includeSubDomains会覆盖printer.office.example.com、放在虚拟主机上的旧博客、第三方托管的status页。只要其中一个没有有效证书,访问过根域名的浏览器就再也打不开它。 - 公网域名下的内网主机名。 像
git.corp.example.com这种用自签名或私有 CA 证书的内网服务,一旦根域名发了includeSubDomains,员工浏览器就会报错,除非每台设备都装了私有 CA 根证书。 - 在正式域名上做开发。 开发人员用 HTTP 访问
dev.example.com:8080,策略生效后会被强制改成 HTTPS。本地开发请用单独的域名。 - 清浏览器缓存不是解决办法。 Chrome 可以在
chrome://net-internals/#hsts删除动态记录,但删不掉预加载条目,而且你的用户也不会去删。 - 只有首页带头。 在应用代码里设置响应头,往往漏掉静态文件和错误页。应该在 Web 服务器或反向代理层统一设置。
- 证书过期等于宕机。 这是“用户无路可退”的代价。要监控到期时间和整条证书链,参见证书链。
HSTS、HTTPS DNS 记录与浏览器默认 HTTPS
现在推动浏览器走 HTTPS 的机制不止 HSTS,但只有 HSTS 同时禁止绕过证书错误。RFC 9460(2023 年 11 月)定义了 HTTPS 类型的 DNS 记录,第 9.5 节规定:客户端发现某主机有 HTTPS 记录时,应把 http:// 请求当成收到 307 跳转到 https://,“类似 HSTS”。它还允许客户端对这类源站取消“绕过证书错误”的选项,并要求发布该记录的源站不能依赖这种绕过。
主流浏览器自己也会优先尝试 HTTPS:hstspreload.org 指出 Chrome 和 Safari 无论有没有 HSTS 都会自动把 HTTP 导航升级为 HTTPS。但这种升级在 HTTPS 失败时会退回 HTTP,攻击者正好利用这一点;HSTS 没有回退。
| 机制 | 信号在哪里 | 保护首次访问 | 会退回 HTTP | 禁止绕过证书错误 |
|---|---|---|---|---|
| 301 跳转 | HTTP 响应 | 否 | 是(可被剥离) | 否 |
| 浏览器自动升级 HTTPS | 浏览器功能 | 是,尽力而为 | 是 | 否 |
| HTTPS DNS 记录(RFC 9460) | DNS | 是,前提是 DNS 可信 | 取决于客户端 | 可选(MAY) |
| HSTS 头(RFC 6797) | HTTPS 响应 | 否 | 否 | 是 |
| HSTS 预加载 | 浏览器源代码 | 是 | 否 | 是 |
上线前检查清单
下面每一项都确认之前,不要把 max-age 调到一个月以上:
常见问题
HSTS 的 max-age 设多少合适?
先用 max-age=300 试水,再按一周、一个月逐级拉长,期间盯住子域名有没有打不开。长期值一般是一年或两年:max-age=31536000(365 天)或 max-age=63072000(730 天)。一年也是 hstspreload.org 收录的最低要求。
在 HTTP 响应里加 HSTS 头有用吗?
没用。RFC 6797 规定浏览器必须忽略明文 HTTP 响应里的 Strict-Transport-Security 头,否则中间人可以随意注入或删掉它。正确做法是 HTTP 先 301 跳到 HTTPS,在 HTTPS 响应里再发这个头。
HSTS 开了以后怎么关掉?
在 HTTPS 响应里发 Strict-Transport-Security: max-age=0,浏览器下次访问时就会删除策略。没再来访问的浏览器会一直保留旧策略,直到原来的 max-age 到期。如果域名已经进了预加载列表,还要去 hstspreload.org 申请移除,并等浏览器发版,通常要几个月。
HSTS 网站证书报错时,用户能点“继续访问”吗?
不能。对已知的 HSTS 主机,浏览器必须把证书或 TLS 错误当作致命错误,不提供绕过入口。所以开了 HSTS 一定要做证书到期监控,证书过期对老访客来说就是整站不可用。
要不要把域名提交到 HSTS 预加载列表?
只有在确定所有子域名(包括内网子域名)都能长期提供有效 HTTPS 时才考虑。hstspreload.org 现在明确写着推荐 HSTS、不推荐预加载,因为 Chrome 和 Safari 已经会自动把大多数访问升级到 HTTPS,而从列表移除非常慢。