你按安全清单把域名提交到了 hstspreload.org。几周后它进了 Chrome 和 Firefox 的内置列表。然后你要回滚:可能要卖域名、可能证书断了想临时回到 HTTP、可能要下线。你把 Strict-Transport-Security header 删了。浏览器照样对你的域名和所有子域强制 HTTPS(因为 preload 必须勾 includeSubDomains)。这是设计如此:preload 把域名编进了浏览器二进制,所以光改服务端没用。
最快的修法(90% 的情况):你几乎肯定根本不用从 preload 移除。 大多数”HSTS preload 卡死”工单其实是”证书过期我们慌了”。在 HTTPS 上重新签一张有效证书,症状几分钟就没了。只有当你真的需要让 apex 或某个子域永远跑明文 HTTP 时,才去走移除流程,而且即便如此也要做好长达数月的尾巴的准备。
先判断你属于哪一类
动手前先对号入座。正确的做法很少是”从 preload 列表移除”。
| 症状 | 真正原因 | 最快修法 |
|---|---|---|
NET::ERR_CERT_DATE_INVALID,没有”继续”按钮 | preload 域名证书过期 | 重新签证书(第 1 步),不用移除。 |
新子域还没敲完就报 ERR_SSL_PROTOCOL_ERROR | includeSubDomains 把它也强制成 HTTPS | 给子域装真证书,或把它从 preload 父域挪走(第 2 步)。 |
内部 staging NET::ERR_CERT_AUTHORITY_INVALID,无法跳过 | preload 父域下用了自签名证书 | staging 改用单独的、非 preload 域名(第 2 步)。 |
| 只在酒店 / 机场 Wi-Fi 上打不开 | 强制门户拦不到 HTTPS | 让用户先打开 http://neverssl.com;不是域名的问题。 |
| 你在卖 / 下线这个域名 | 新主人没法跑 HTTP | 提交移除并等推送完成(第 3、4、6 步)。 |
常见原因
按事故复盘里出现的频率排序。
1. 某个子域忘了,现在却需要 HTTP
你 preload 时勾了 includeSubDomains。几个月后有人开了 legacy.yourdomain.com,跑在只支持 HTTP 的服务上(旧的 IoT 控制台、打印机后台、CI artifact 服务器)。浏览器永远拒绝用 HTTP 打开它,因为父域的 preload 条目强制下面所有东西走 HTTPS。
怎么判断:新子域返回 ERR_SSL_PROTOCOL_ERROR 或 “This site can’t be reached”,用户连 http:// 都还没敲,Chrome 就先把协议改成 HTTPS 了。
2. 证书过期了,新证书一时签不出来
证书过期,源站 HTTPS 挂了。用户看到 NET::ERR_CERT_DATE_INVALID,而且没有”继续访问”链接——preload 设了严格位,不给点穿。preload 域名没有点穿。
怎么判断:证书已过期,并且在 chrome://net-internals/#hsts 查域名显示为 static。没有 bypass。
3. 域名所有权转出了,新主人需要 HTTP
你卖掉或释放了域名。新主人继承了 preload 条目——在域名从列表移除并且浏览器缓存老化之前,他不能给 apex 或任何子域跑明文 HTTP。
怎么判断:新主人在别的域名能部署,但只要把 yourdomain.com 指过去且没 HTTPS 就崩。
4. 自签名 / 内部 CA 证书不能再被接受
preload 之前,用户能对内部 staging 服务器点穿证书警告。preload 之后没法点穿。内部 QA 团队被锁在 staging 外面。
怎么判断:内部 staging staging.yourdomain.com 报 NET::ERR_CERT_AUTHORITY_INVALID,没有跳过按钮。
5. 公共 Wi-Fi 强制门户拦不到
很多强制门户靠拦截明文 HTTP 请求、重定向到登录页工作。在你的 preload 域名上浏览器根本不发 HTTP 请求,门户触发不了,用户也登不上。基础 Wi-Fi 能通,但用着像断网。
怎么判断:用户反馈站点在酒店 / 机场 Wi-Fi 上不行、别的地方行,其他非 preload 站点正常。临时办法:让用户先打开一个已知走 HTTP 的页面,比如 http://neverssl.com,把门户弹出来。
6. 通配符证书配错代价被放大
没 preload,通配符配错至少给个证书警告、能点穿排错。有 preload,任何证书不匹配都是硬挂、没 bypass——小错变事故。
怎么判断:preload 域名上任何证书不匹配 = 直接 NET::ERR_CERT_COMMON_NAME_INVALID,没法跳过。
开始前
- 确认域名真在 preload 上:访问
https://hstspreload.org/?domain=yourdomain.com。pending 和已发布的条目报告方式不同,注意你看到的是哪种。 - 看清楚用户主要用什么浏览器。Chrome / Firefox / Safari 各自有自己的 preload 列表、更新节奏不一样,而 Chromium 系(Edge / Brave / Vivaldi / Opera)都继承 Chrome 的列表。
- 把当前证书状态记下来:过期日、issuer、证书链。
- 想清楚你真正的目标:是”永远回滚”,还是”接受 HTTPS-only,把证书修对就行”。后者几乎总是答案。
需要收集的信息
curl -sI https://yourdomain.com | grep -i strict-transport——HSTS header 还在发吗、里面有没有preload?- 域名在
chrome://net-internals/#hsts(顶部查询框)里的状态。static= preload 条目;dynamic= 你服务器 header 设的。 - nginx / app 里原来配的
max-age和includeSubDomains值。 - 当前在用的所有子域、每个能不能正常出 HTTPS。
- 当前有多少用户被卡 / 不便。
一步步修
诚实顺序:短期路径 = 把 HTTPS 恢复;长期路径 = 从 preload 列表移除。先试短期路径。
第 1 步:先看是不是只要把 HTTPS 恢复就行
大多数”HSTS preload 卡死”工单其实是证书过期。重新签。
sudo certbot renew --force-renewal
sudo systemctl reload nginx
# 确认新的有效期窗口:
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -dates
有效证书拉回来,问题就没了,根本不用碰 preload 列表。如果在 CDN 后面,“重签”可能简单到只是开启服务商的免费边缘证书——把域名挂到 Cloudflare 代理后面,几分钟就发一张。
第 2 步:内部 staging 需要自签名,就别用 preload 父域的子域
最干净的方案是别在 preload 父域下用内部 / 自签名的工作。staging 挪到单独注册的域名,比如 staging.yourdomain-internal.com——不在 preload 列表上。
实在要保留这个子域名,就装一张公网受信的真证书。Let’s Encrypt 用 DNS-01 challenge 给内部专用主机名也能签,因为 DNS-01 不需要入站 HTTP——只要一条 TXT 记录。
第 3 步:别再带 preload(必要时再发 max-age=0)
这一步是大多数教程搞错的地方。要变成可移除,你不需要 max-age=0。按 hstspreload.org/removal/ 的官方要求,你只需发一个不再包含 preload 指令的有效 HSTS header。HSTS 本身继续生效没关系。
所以最小改动就是把 header 里的 preload 去掉:
# 满足移除条件:HSTS 有效,但没有 preload 指令。
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
如果你还想让浏览器彻底忘掉 HSTS(比如你确实需要 apex 上跑 HTTP),就发 max-age=0。按 RFC 6797 §6.1.1,这会在浏览器下次发起 HTTPS 请求时清掉动态缓存:
# 客户端重新走 HTTPS 后,对非 preload 客户端关闭 HSTS。
add_header Strict-Transport-Security "max-age=0" always;
不管哪种,都要重载 nginx(sudo nginx -t && sudo systemctl reload nginx)。这两种改动都碰不到编进浏览器二进制的静态 preload 条目——但发一个不带 preload 的 header 是第 4 步的前置条件。
第 4 步:到 hstspreload.org 提交移除申请
访问 https://hstspreload.org/removal/,输入域名。要被接受,域名必须满足(截至 2026 年 6 月,照表单原文):
- 当前已 preload 或待 preload(pending),且经由
hstspreload.org。 - 用有效证书提供 HTTPS。
- 发一个不包含
preload指令的有效 HSTS header。
都满足,域名就进移除队列。之后按浏览器分别生效:
- Chrome:移除先合入 Chromium,再随下一个 stable 大版本发布。截至 2026 年 6 月 Chrome 是 4 周一个 stable(2026 年 9 月起随 Chrome 153 改为 2 周)。Google 自己的估计是,移除要 6-12 周才能到大多数 Chrome 用户。
- Firefox:节奏类似;Mozilla 从 Chromium 源重新生成自己的列表。
- Safari / WebKit:独立流程,通常更慢。
所以大多数及时更新的用户会在大约 6-12 周内不再强制 preload。真正的长尾——很少更新浏览器的用户——可能还会带着旧的内置列表好几个月。两头都要计划:多数人快,掉队的人慢。
第 5 步:当下受影响的用户怎么救
浏览器里已经有 preload 条目的用户没法轻易绕。真有用的办法:
- 换浏览器:Firefox 和 Chrome 的列表略有差异,可能其中一个还没收录你的域名。(Edge / Brave / Vivaldi / Opera 都继承 Chrome 列表,在它们之间换没用。)
- 内部用户的话立刻部一张公网受信的真证书。把域名挂到 Cloudflare 代理后面,几分钟就拿到可用的边缘证书。
- 开发调试的话
chrome://net-internals/#hsts→ “Delete domain security policies” 删动态条目,但删不了静态 preload。用处有限,但当你怀疑是过期的动态条目(而非 preload 列表)时值得一试。
第 6 步:必要时才永久回滚
要永久回滚(卖域名 / 下线):
- 发一个不带
preload的 HSTS header(或max-age=0)——第 3 步。 - 在
hstspreload.org/removal/提交移除——第 4 步。 - 等下一个去掉你条目的 stable Chrome / Firefox(多数用户约 6-12 周)。
- 接受很少更新浏览器的用户还会继续强制 HTTPS 数月、甚至数年。
- 告诉接手域名的人这个约束:在很长尾的浏览器装机量上,HTTP 都跑不了。
怎么确认修好了
https://hstspreload.org/?domain=yourdomain.com显示域名 not preloaded(移除发布后)。curl -sI https://yourdomain.com | grep -i strict-transport不返回 header、返回max-age=0,或至少返回一个不带preload的 header。- 在更新过的 Chrome 上,于
chrome://net-internals/#hsts查域名,静态条目返回 “not found”。 - apex 下的新子域能跑明文 HTTP、浏览器不改协议——只有在移除发布且测试用的浏览器已更新后才成立。用一个你从没访问过该站点的浏览器测试,避免过期的动态条目把结果掩盖掉。
长期预防
- 把
hstspreload.org提交当作单向门。除非你能保证所有现有和未来子域永远只走 HTTPS、并且有一套验证过的证书续签流水线,否则不要提交。 - 生产环境用
max-age=31536000; includeSubDomains(一年)但不带preload指令。你拿到几乎全部安全收益,同时保持可逆。等有了一整年干净 HTTPS uptime 再考虑提交 preload——或者干脆别提交。 - 通配符域、内部服务域、多租户共享域永远不要提交 preload。preload 只留给公开的市场 apex。
- 证书过期前 25+ 天就告警,不是 7 天。preload 让证书断档变成没有用户 bypass 的硬故障。
- 在 DNS-as-code 仓库里把 preload 这个约束写清楚,免得未来工程师把不支持 HTTPS 的服务挂到子域。
常见坑
- 把 nginx 里 HSTS header 删了就以为完事。preload 是浏览器二进制里的、不是你服务器配的。
- 以为移除表单需要
max-age=0。它只要一个不带preload指令的有效 HSTS header;max-age=0是可选的、只对清动态缓存有意义。 - 为了”安全评分”提交 preload,不理解它不可逆。大多数安全团队会接受一年的
max-age拿到同等合规收益。 - 想在生产浏览器里绕过 preload。没有面向用户的 bypass——
thisisunsafe只能关掉证书警告 interstitial,对 preload 的强制无效。 - 把子产品放在 preload 父域的子域下,然后惊讶自签名证书不能用。
- 以为移除明天就生效。它跟下一个浏览器大版本一起发,不按需——多数 Chrome 用户约 6-12 周。
FAQ
Q:移除表单到底要什么——需要 max-age=0 吗?
不需要。截至 2026 年 6 月,hstspreload.org/removal/ 表单只要求你的域名仍用有效证书提供有效 HSTS header,且把 preload 指令去掉。你可以保留正常的 max-age 和 includeSubDomains。只有当你另外还想对非 preload 客户端关闭 HSTS 时,才发 max-age=0。
Q:从提交移除到真实用户生效要多久?
Google 自己的说法是,移除合入 Chromium 并随 stable 大版本发布后(截至 2026 年 6 月 Chrome 每 4 周发一版),约 6-12 周到大多数 Chrome 用户。Firefox 节奏类似,Safari 是更慢的独立流程。极少更新浏览器的长尾用户可能还会带着旧列表好几个月。
Q:在 Chrome 里删个缓存条目能绕过 preload 吗?
你能通过 chrome://net-internals/#hsts → “Delete domain security policies” 清动态条目(你服务器 header 设的)。但你清不了静态 preload 条目——它编进了浏览器。用一个还没收录该条目的浏览器,是唯一真的 workaround。
Q:有没有靠 max-age 实现的”软 preload”?
有。Strict-Transport-Security: max-age=31536000; includeSubDomains(一年,不带 preload)能拿到几乎全部安全收益、没有不可逆的代价,大多数合规框架都接受。
Q:安全扫描说我不 preload 就扣分,要不要提交?
扫描器太死板的话,推回去:preload 不可逆,相比长 max-age 的边际安全收益,运维风险更大。NIST 和 CIS 的指南都把长 max-age 视为合规的 HSTS 配置。