HTTPS 站点开启 HSTS 实战:Apache / Nginx / IIS 配置 + preload 提交流程 + 故障回滚的两条路
HSTS(HTTP Strict Transport Security)是 RFC 6797 定义的强制 HTTPS 机制:服务端通过响应头声明本域名只能走 HTTPS,浏览器在指定时间窗内即使用户输入 http:// 也会自动转 https://。它是修复"明文 HTTP 时代降级攻击"的核心防御,也是 Google Lighthouse 和 SecurityHeaders.com 等评分工具的硬指标。
但 HSTS 上线一旦配错,站点会彻底打不开 + 用户浏览器记 1 年解不开——这是 HTTPS 配置里风险最高的一类操作。这一篇把 HSTS 的原理、Apache / Nginx / IIS / Caddy 各自的正确配置、preload 提交流程、回滚的两种紧急方法、子域风险、与 CDN 的协同、Lighthouse 评分、HTTPS 自动续签等全部讲透,附 FAQ 与故障案例。
一、HSTS 工作原理
HSTS 的语义非常简单。服务端响应任何 HTTPS 请求时附带一条头:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
这条头告诉浏览器:"这个域名(含子域)从现在起 63072000 秒(约 2 年)内只能走 HTTPS,自动把所有 http:// 请求改成 https://,用户即使在地址栏输 http:// 浏览器也不会真发 HTTP 请求"。
1.1 三个参数的真实含义
| 参数 | 必选 | 含义 | 风险 |
|---|---|---|---|
| max-age | 是 | HSTS 有效期(秒) | 过短无意义;过长且配错会锁站 |
| includeSubDomains | 否 | 是否覆盖子域 | 子域有非 HTTPS 服务时会全部断 |
| preload | 否 | 申请加入浏览器预置 HSTS 列表 | 提交后撤回流程长达数月 |
2026 年的安全 baseline 推荐:
- 正式生产:
max-age=31536000(1 年)+includeSubDomains+preload; - 测试灰度:
max-age=600(10 分钟)观察是否有故障; - 上线渐进:先
max-age=3600(1 小时),稳定 1 天后改86400(1 天),稳定 1 周后改31536000(1 年)。
1.2 浏览器侧的处理
浏览器收到 HSTS 头后写入本地的 HSTS 缓存(Chrome 在 Site Settings,Firefox 在 about:preferences#privacy)。在 max-age 内:
- 用户输入
http://yoursite.com→ 浏览器内部直接改成https://yoursite.com发请求; - 遇到自签证书 / 过期证书 → 没有"继续访问"按钮(普通 HTTPS 错误能点继续,HSTS 模式不能);
- HTTPS 握手失败 → 直接报错,不会降级到 HTTP。
这种"无逃生通道"是 HSTS 的安全特性——一旦中间人篡改证书,浏览器拒绝建立连接,绝不会连到伪造站。但也是最大风险点:证书过期就是站点宕机,而且用户绕不过去。
二、Apache 配置
<VirtualHost *:443>
ServerName yoursite.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/yoursite.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/yoursite.com/privkey.pem
# HSTS 头(生产)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</VirtualHost>
# HTTP 强制跳 HTTPS(HSTS 前提)
<VirtualHost *:80>
ServerName yoursite.com
Redirect permanent / https://yoursite.com/
</VirtualHost>
关键点:
Header always set(不是Header set):always 修饰符让头在所有响应(含 4xx / 5xx 错误响应)都附带——这是 preload 提交的硬性要求,错过会被 hstspreload.org 拒。- 必须先有 HTTP→HTTPS 301 跳转:HSTS 只对 HTTPS 响应有效,纯 HTTP 收不到 HSTS 头。所以必须 80 端口先跳到 443。
- 需 mod_headers:
LoadModule headers_module modules/mod_headers.so,多数发行版默认装。
三、Nginx 配置
server {
listen 443 ssl http2;
server_name yoursite.com;
ssl_certificate /etc/letsencrypt/live/yoursite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yoursite.com/privkey.pem;
# HSTS 头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# 防 MIME 嗅探 + 内容嵌入
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
# ... 其它配置
}
# HTTP → HTTPS 301
server {
listen 80;
server_name yoursite.com www.yoursite.com;
return 301 https://yoursite.com$request_uri;
}
Nginx 的 add_header 一定要带 always 参数——和 Apache 同理,HSTS 头必须在所有响应(含错误响应)出现。
3.1 Nginx add_header 的"覆盖陷阱"
Nginx 的 add_header 在嵌套 location / server / http 块时不会自动继承——内层的 add_header 会完全覆盖外层的,导致 HSTS 在子 location 里失效:
server {
add_header Strict-Transport-Security "..."; # 全局
add_header X-Frame-Options "...";
location /api {
add_header Cache-Control "no-store"; # ⚠ 危险!会覆盖外层 HSTS!
}
}
修复:内层 add_header 必须重写所有外层头:
location /api {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Cache-Control "no-store";
}
这是 Nginx 的设计陷阱,2026 年仍未改。任何 location 块加 add_header 都要把全局头重新声明一遍。
四、IIS 配置
原帖提到的 codeplex 第三方组件已经下线(codeplex 自身已停止服务)。IIS 8.5+ 内置支持 HSTS:
<!-- web.config -->
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" />
</rule>
</rules>
<outboundRules>
<rule name="Add HSTS">
<match serverVariable="RESPONSE_Strict_Transport_Security" pattern=".*" />
<conditions>
<add input="{HTTPS}" pattern="on" />
</conditions>
<action type="Rewrite" value="max-age=31536000; includeSubDomains; preload" />
</rule>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>
IIS 10 (Windows Server 2019+) 还有更现代的 <hsts> 元素:
<site name="MySite">
<hsts enabled="true" max-age="31536000" includeSubDomains="true" preload="true" redirectHttpToHttps="true" />
</site>
五、Caddy(最简单)
Caddy 默认就启用 HSTS,无需配置:
yoursite.com {
reverse_proxy localhost:8080
# HSTS、HTTP→HTTPS、自动证书都已默认开启
}
要自定义 HSTS 参数:
yoursite.com {
header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
}
六、preload 提交流程
提交到 hstspreload.org 后,你的域名进入 Chrome / Firefox / Safari / Edge 等所有现代浏览器的预置 HSTS 列表——即使用户从未访问过你的站点,浏览器也直接把它当 HSTS 域名处理。这是最强的 HSTS 保护级别。
6.1 提交前的硬性要求
- 所有 HTTP 请求必须 301 重定向到 HTTPS(不能 302、不能 200);
- HSTS 头
max-age必须 ≥ 31536000(1 年); - 必须包含
includeSubDomains; - 必须包含
preload; - 所有子域必须支持 HTTPS(包括邮件子域 mail.example.com、API 子域 api.example.com 等);
- HTTPS 重定向必须指向同域 HTTPS(不能跳到第三方)。
6.2 提交步骤
- 访问
https://hstspreload.org/; - 输入域名,点 "Check HSTS preload status and eligibility";
- 系统自动检测 HSTS 头是否符合要求;
- 勾选两个声明(确认理解风险),点 Submit;
- 等待 Google 审核(通常 1-2 周),通过后进入下一个 Chrome 稳定版的 preload 列表(约 6 周后所有用户)。
6.3 撤回 preload 的代价
提交后想撤回需要走 https://hstspreload.org/removal/:
- 提交撤销请求;
- 等待审核(数周到数月);
- 新版浏览器移除你的域名后,仍未升级的旧版浏览器还会在内置列表里保留——这部分用户的浏览器要等他们升级才会更新。
实际撤销时间:从你想撤到所有用户都不再 preload 你的域名,普遍 6-12 个月。所以提交前必须百分百确认未来一年内不会想撤。
七、HSTS 故障案例与回滚
7.1 经典故障:证书过期 + HSTS 锁死
2018 年某知名大厂证书续签失败,HSTS preload 已生效。结果:
- 所有用户访问全 SSL 错误;
- 用户无法点"继续访问"绕过;
- 修复证书后 + DNS 全部传播,仍有部分用户因 HSTS 缓存 24 小时内打不开;
- 预计直接损失百万美元。
这就是为什么 HSTS 必须配证书自动续签 + 监控告警。
7.2 紧急回滚的两条路
万一站点上线 HSTS 后想回滚(max-age 还在生效),唯一办法:
- 把 HSTS 头改成 max-age=0:
Strict-Transport-Security: max-age=0。用户下次成功访问时本地 HSTS 缓存清空。但前提是用户能访问到 HTTPS 站点; - 用户手动清浏览器 HSTS 缓存:Chrome 输入
chrome://net-internals/#hsts,"Delete domain security policies" 输域名。但要求每个用户自己操作。
已经 preload 的无法仅靠服务端回滚——必须走撤回流程,慢得多。
八、HSTS 与 CDN 的协同
站点用 Cloudflare / 阿里云 CDN / 腾讯云 EdgeOne 时,HSTS 头有几个常见问题:
8.1 CDN 默认会代加 HSTS 头吗?
看 CDN:
- Cloudflare:免费版不自动加,需在 SSL/TLS → Edge Certificates → HSTS 手动开启;
- 阿里云 CDN:在 HTTPS 配置里有 HSTS 开关;
- 腾讯云 EdgeOne:策略 → 安全 → HSTS。
建议只在源站或 CDN 一侧设置,不要两边都设——避免不一致或重复头。
8.2 CDN 缓存 HSTS 头
HSTS 是 HTTP 响应头,会被 CDN 缓存。如果你想紧急修改 HSTS(比如 max-age 改 0 回滚),同时要清 CDN 缓存,否则用户拿到的还是旧 HSTS 头。
九、与其它安全头的组合
HSTS 是安全头家族的入门。生产建议同时配以下几个:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; ..." always;
每条都有专门作用,配齐之后 SecurityHeaders.com 评分能拿到 A+。
十、SEO 视角的 HSTS
HSTS 对 SEO 的直接影响:
- Google 把 HTTPS 列为正向排名因素:HSTS 强制 HTTPS 是这个因素的一部分;
- 避免 http→https 重定向链:用户和爬虫第一次访问 http:// 会被重定向,HSTS 之后浏览器自己改 URL 不再走重定向,节省一次往返;
- Lighthouse Best Practices 评分:HSTS 加分项;
- 避免混合内容警告:HSTS 强制所有资源走 HTTPS。
十一、HTTPS 自动续签是 HSTS 上线的硬前提
2026 年的标准做法:
# Let's Encrypt + Certbot 自动续签
sudo certbot --nginx -d yoursite.com --staple-ocsp
sudo certbot renew --dry-run
sudo systemctl enable --now certbot.timer
certbot.timer 是 systemd 定时器,每天凌晨自动检查证书是否到期,到期前 30 天自动续签 + reload Nginx。配 + HSTS 才稳。
常见问题解答
HSTS 头加上之后浏览器没生效,怎么排查?
三个检查点:① 用 curl -I https://yoursite.com/ 看响应头里是否真有 Strict-Transport-Security;② 用 https://www.ssllabs.com/ssltest/ 跑一遍,HSTS 部分会评估;③ 浏览器开 DevTools → Network → 选一个请求看响应头。如果 curl 有但浏览器没,可能是 CDN 在缓存层去掉了。
不小心提交了 preload 怎么办?
到 hstspreload.org/removal/ 提交撤销请求,等审核通过 → 进入下一个 Chrome 版本 → 6-12 个月内用户陆续不再 preload。期间所有用户继续按 HSTS 处理。所以提交前一定百分百确认。
子域不支持 HTTPS,能开 includeSubDomains 吗?
不能。开了之后所有子域都被强制 HTTPS——子域如果没 HTTPS(比如老的 ftp.yoursite.com 或邮件 mail.yoursite.com),就全部不可访问。先把所有子域升级到 HTTPS 再开 includeSubDomains。
https://yoursite.com 能访问,http://yoursite.com 也想能访问,HSTS 会强制吗?
HSTS 是浏览器机制——已访问过 HTTPS 的浏览器后续会自动转。但从未访问过 HTTPS 的浏览器第一次访问 http:// 仍走 HTTP(除非你的域名在 preload 列表)。所以 HTTP→HTTPS 重定向必须始终保留,不能依赖 HSTS 替代。
HSTS 与 HTTP/2 / HTTP/3 的关系?
HSTS 是协议层面的,HTTP/2 / 3 是传输层面的,独立无冲突。HSTS 头在所有 HTTP 版本响应里都有效。HTTP/3(QUIC)甚至直接要求 HTTPS(QUIC 强制加密),与 HSTS 哲学一致。
我用 Cloudflare 免费版,HSTS 能用 preload 吗?
能。Cloudflare 免费版的 HSTS 设置在 SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS),勾选 max-age=1 year 和 No-Sniff Header 即可。preload 在同界面有"Apply HSTS Preload"开关。但要注意,Cloudflare 配的 HSTS 是从 CF 边缘节点发出的,源站不需要再配。
HSTS 在内网/局域网域名(如 yoursite.local)有效吗?
有效,但不能 preload——Chrome 等浏览器只对公网可解析的真域名 preload。内网域名只能靠服务器端发 HSTS 头让浏览器记住,不能用 hstspreload.org 提交。
开了 HSTS 后,如何让特定 URL 不走 HTTPS?
不能。HSTS 是域名级别的强制,无法对单个 URL 例外。如果某个老接口必须 HTTP(比如老硬件 webhook),只能用子域分离——主域名 HSTS,老接口走 legacy.yoursite.com 不开 HSTS(前提是主域名没开 includeSubDomains 或子域单独排除)。
HSTS 头会影响 API 调用吗?
不会。HSTS 只对浏览器有意义——浏览器才会读 HSTS 头并改本地缓存。命令行工具(curl / wget / postman)默认不读 HSTS(除非加 --http2-prior-knowledge 或类似强制)。所以 API 客户端调用不受影响,但要确认它们用 HTTPS。
能给某些子域单独配不同的 HSTS max-age 吗?
能,但不要这样做。用 includeSubDomains 时父域 HSTS 覆盖所有子域;不用 includeSubDomains 时每个子域单独配自己的 HSTS。混用很容易让 preload 提交失败或浏览器行为不一致。建议所有子域用相同的 HSTS 策略。