你按照安全清单提交域名到 hstspreload.org。几周后它进了 Chrome 和 Firefox 的内置列表。然后你要回滚——可能要卖域名、可能证书供应商挂了想临时回到 HTTP、可能要下线。你把 Strict-Transport-Security header 删了,把 max-age 设 0。没用。浏览器继续对你的域名和所有子域(你 preload 时必须勾 includeSubDomains)强制 HTTPS。这是设计如此:preload 把你的域名编译进了浏览器二进制本身。修复路径有、但慢——以月计、不是分钟。提交前没人意识到”只能用 HTTPS”这个约束有多严。
常见原因
事故复盘里这些场景出现最多。
1. 某个子域忘了,现在却需要 HTTP
你 preload 时勾了 includeSubDomains。几个月后有人开了 legacy.yourdomain.com,跑在只支持 HTTP 的服务上(旧的 IoT 控制台、打印机后台、CI artifact 服务器)。浏览器永远拒绝打开,因为父域的 preload 强制下面所有东西走 HTTPS。
怎么判断:新子域返回 ERR_SSL_PROTOCOL_ERROR 或 “This site can’t be reached”,用户连 http:// 都还没敲 Chrome 就先升 HTTPS 了。
2. 证书过期了,新证书一时签不出来
证书过期了,源站 HTTPS 挂了。用户看到 NET::ERR_CERT_DATE_INVALID,而且没有 “继续访问” 选项——preload 设了严格位,不给点穿。
怎么判断:证书已过期 + chrome://net-internals/#hsts 显示你的域名是 static。没有 bypass。
3. 域名所有权转出了,新主人需要 HTTP
你卖掉或释放了域名。新主人继承了 preload 条目——在从列表移除并且浏览器缓存老化之前,他不能给任何子域跑明文 HTTP。
怎么判断:新主人在别的域名能部署,但只要把 yourdomain.com 指过去并且没 HTTPS 就崩。
4. 自签名 / 内部 CA 证书不能再被接受
preload 之前,用户能对内部 staging 服务器点穿证书警告。preload 之后没法点穿。内部 QA 团队被锁在 staging 外面。
怎么判断:内部 staging staging.yourdomain.com 报 NET::ERR_CERT_AUTHORITY_INVALID,没有 bypass 按钮。
5. 公共 Wi-Fi 强制门户拦不到
很多公共 Wi-Fi 靠拦截 HTTP 请求工作。preload 域名上,浏览器拒绝任何 HTTP 层重定向,用户连不上酒店 / 机场 Wi-Fi 强制门户。基础 Wi-Fi 能通,但用户用着像断网。
怎么判断:用户反馈站点在酒店 / 机场 Wi-Fi 上不行、别的地方行。其他非 preload 站点正常。
6. 通配符证书配错代价被放大
没 preload,通配符配错至少给个证书警告、能点穿排错。有 preload,任何证书不匹配都是硬挂、没 bypass——小错变事故。
怎么判断:preload 域名上任何证书不匹配 = 直接 NET::ERR_CERT_COMMON_NAME_INVALID,没法跳过。
开始前
- 确认域名真在 preload 上:
https://hstspreload.org/?domain=yourdomain.com。 - 看清楚用户主要用什么浏览器——Chrome / Firefox / Safari 各自有自己的 preload 列表、更新节奏不一样。
- 把当前 SSL 证书状态记下来(过期日、issuer、链)。
- 想清楚你的需求:是”永远回滚”,还是”接受 HTTPS-only,把证书修对就行”。
需要收集的信息
curl -I https://yourdomain.com输出——HSTS header 还在发吗?- 浏览器 DevTools → application / storage →
chrome://net-internals/#hsts里查你的域名。 - nginx / app 里原来配的 max-age 和 includeSubDomains 值。
- 当前在用的所有子域、每个能不能正常出 HTTPS。
- 当前有多少用户被卡 / 不便。
一步步修
诚实顺序:短期路径 = 把 HTTPS 恢复;长期路径 = 从 preload 列表移除。
第 1 步:先看是不是只要把 HTTPS 恢复就行
90% 的”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 移除。
第 2 步:内部 staging 需要自签名,就别用 preload 父域的子域
最干净的方案是别在 preload 父域下用内部 / 自签名的工作。staging 挪到 staging.yourdomain-internal.com 之类、单独注册的域名——不在 preload 列表上。
实在要保留这个子域名,就装真证书(Let’s Encrypt 用 DNS-01 challenge 内部域名也行,不需要公网 HTTP)。
第 3 步:立刻发 max-age=0,虽然只对非 preload 客户端有效
HTTPS 响应改:
add_header Strict-Transport-Security "max-age=0; includeSubDomains" always;
重载 nginx。任何用 HTTPS 访问过你站点的浏览器(动态 HSTS 缓存)会清掉。这对 preload 列表没用——条目编进浏览器二进制里——但是从列表移除的前置条件。
第 4 步:到 hstspreload.org 提交移除申请
去 https://hstspreload.org/removal/,输入域名。它会检查:
- 你的域名 HTTPS 现在发的是
max-age=0(或不发 HSTS)。 - apex 上检查通过。
都过,域名进移除队列。移除真正生效:
- Chrome:下一个 release(约 6 周),然后约 6 个月推完 stable channel。
- Firefox:类似节奏。
- Safari / WebKit:独立流程,常常更慢。
现实时间线:6-12 个月之后才有显著比例用户不再 preload 你的域名。要按这个节奏计划。
第 5 步:当下受影响的用户,给出 workaround
已经把 preload 缓存进浏览器的用户没法轻易绕。真有用的 workaround:
- Chrome 被卡就用 Firefox(反之亦然)——preload 列表略有差异。
- 用别的浏览器(Brave / Vivaldi 继承 Chrome 列表、Edge 也是)。
- 内部用户的话立刻部一张真证书——把域名挂到 Cloudflare 代理后面,几分钟就拿到边缘证书。
- 开发调试的话
chrome://net-internals/#hsts→ “Delete domain security policies” 删动态条目,但不能删静态 preload。用处有限、但值得一试。
第 6 步:必要时永久回滚
要永久回滚(卖域名 / 下线):
- 在
hstspreload.org/removal/提交移除。 - 等下一个包含移除的 Chrome / Firefox 大版本(约 6 周构建 + 3-6 个月分发)。
- 接受用户在老版浏览器上还会继续强制 HTTPS 好几年。
- 告诉接手域名的人这个约束——很长尾的浏览器装机量上 HTTP 都跑不了。
验证
https://hstspreload.org/?domain=yourdomain.com显示 “not preloaded”(移除生效后)。curl -I https://yourdomain.com不再返回strict-transport-securityheader(或者是max-age=0)。chrome://net-internals/#hsts查你的域名,静态条目返回 “not found”(移除在 Chrome 里发布后)。- 父域下新子域可以跑 HTTP 而不被浏览器拦(仅在移除生效 + 用户浏览器更新后)。
长期预防
- 把
hstspreload.org提交当作单向门。除非你能保证所有子域永远只走 HTTPS、并且有一套验证过的证书续签流水线,否则不要提交。 - 生产环境用 6 个月的
max-age=但不提交 preload,等你有了一整年干净 HTTPS uptime 再考虑提交。 - 通配符域、内部服务域永远不要提交 preload。preload 留给公开的市场域。
- 证书过期前 25+ 天就告警,不是 7 天。preload 让证书断档代价高得多。
- 在 DNS-as-code 仓库里把 preload 这个约束写清楚,未来工程师别不小心把不支持 HTTPS 的服务挂到子域。
常见坑
- 把 nginx 里 HSTS header 删了就以为解决了——preload 是浏览器二进制里的、不是你服务器配的。
- 为了”安全评分”提交 preload 不理解它不可逆——大多数安全团队会接受 6 个月的
max-age拿到同等合规收益。 - 想在生产浏览器里绕过 preload——没有面向用户的 bypass。告诉用户敲
thisisunsafe只对证书警告 interstitial 有效,对 preload 的”无点穿”无效。 - 把子产品放在 preload 父域的子域下,然后惊讶自签名证书不能用。
- 提交移除以为明天就生效——它跟下一个浏览器版本一起发,不按需。
FAQ
Q:从移除到真实用户生效要多久?
提交到 GitHub 仓库的合入要几周。下一个包含移除的 Chrome / Firefox stable 大约 6 周后发布。再 3-6 个月用户群基本更新完。现实窗口 6-12 个月。老浏览器用户可能永远拿不到这个移除。
Q:直接清 Chrome 缓存能绕过吗?
动态 HSTS(你服务器 header 设的)可以通过 chrome://net-internals/#hsts 清。静态 preload 没法清——编进浏览器了。换浏览器是唯一真的 workaround。
Q:有没有”软 preload”,靠 max-age 实现?
有——Strict-Transport-Security: max-age=15552000; includeSubDomains(6 个月)不提交 preload,能拿到 95% 的安全收益、没有不可逆的代价。大多数合规框架都接受。
Q:安全扫描说我不 preload 就扣分,要不要提交?
扫描器太死板的话,推回去:preload 不可逆、运维风险大于相比长 max-age 的边际安全收益。多数现代框架(NIST / CIS)认这个差异。