Web 服务器

开启 HSTS(HTTP 严格传输安全)

作者: · 最后核实

开启 HSTS 就是在 HTTPS 响应里加 Strict-Transport-Security 头,浏览器收到后在 max-age 内只用 HTTPS 访问网站。先用 300 秒的 max-age 上线,确认没问题后分阶段提高到一到两年,再考虑 includeSubDomains 和预加载。

配置写法覆盖 Nginx、Apache 2.4、Caddy 2、IIS 10 和宝塔面板。

前提条件

HSTS 会去掉退回 HTTP 的余地,所以 HTTPS 必须先稳定。

  • 证书有效且已配置自动续期(见 证书自动续期)。
  • 已经配置 HTTP 跳转 HTTPS(见 HTTP 跳转 HTTPS)。
  • 没有混合内容;打算加 includeSubDomains 的,所有子域名都已支持 HTTPS。

第一步:用很短的 max-age 加上响应头

各服务器都是在 HTTPS 站点里加同一个响应头,max-age 先设 300 秒。

Nginx,写在 listen 443 ssl 的 server 块里:

add_header Strict-Transport-Security "max-age=300" always;

location 块里只要写了自己的 add_header,就不会继承上层的 add_header,需要在这些 location 里再写一遍。

Apache,写在 *:443 虚拟主机里(需要 a2enmod headers):

Header always set Strict-Transport-Security "max-age=300"

Caddy,写在站点块里:

header Strict-Transport-Security "max-age=300"

IIS,写在网站的 web.config 里:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Strict-Transport-Security" value="max-age=300" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

改完后重载服务。

第二步:分阶段提高 max-age

max-age 按 5 分钟、1 天、1 周、1 年、2 年逐级提高,每一级观察几天没问题再往上调:

max-age=300          (5 分钟)
max-age=86400        (1 天)
max-age=604800       (1 周)
max-age=31536000     (1 年)
max-age=63072000     (2 年)

第三步:加上 includeSubDomains

确认每个子域名都能提供 HTTPS 后,把策略扩展到子域名:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

第四步(可选):申请预加载

想被内置到浏览器里,主域名要返回不少于 max-age=31536000 并带 includeSubDomains; preload 的头,证书有效,并且在同一主机上先从 HTTP 跳到 HTTPS,再跳到其他地址:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

然后到 hstspreload.org 提交。提交前先看清楚那里关于移除的说明。

验证

配置生效后,HTTPS 响应里能看到 Strict-Transport-Security 头。检查线上的响应头:

curl -sI https://example.com | grep -i strict-transport-security

HSTS 检查 还会列出预加载条件是否满足。再用 SSL 证书检查 确认背后的证书,或在终端运行:

curl ssl.sh/example.com

自动续期

HSTS 会把证书问题直接变成网站打不开:证书过期或不匹配时,浏览器显示的错误页没有“继续访问”的选项,所以续期必须可靠。使用每天至少运行一次、支持 ARI 的 ACME 客户端,续期后重载服务,并用 ssl.sh 监控(开发中) 盯住线上证书。证书有效期在缩短:2026 年 3 月 15 日起最长 200 天,Let's Encrypt 默认证书 2027 年 2 月 10 日起 64 天,2029 年 3 月 15 日起最长 47 天。

常见问题排查

  • 部分页面没有 HSTS 头:Nginx 某个 location 写了自己的 add_header 导致不继承,或者没加 always,错误页面不带这个头。
  • 证书出错时用户无法跳过:这正是 HSTS 的作用,修好证书,见 HSTS 无法跳过 和 NET::ERR_CERT_DATE_INVALID。
  • 加 includeSubDomains 后某个内网子域名打不开:给它配置有效证书,或在主域名上把 max-age 改为 0,等浏览器忘掉策略。
  • 在 HTTP 响应里发送了 HSTS 头:无害但会被忽略,浏览器只接受 HTTPS 响应里的 HSTS。
  • 预加载提交被拒绝:hstspreload.org 的检查结果会列出不满足的条件,常见的是主域名没有跳转或 max-age 不足一年。

常见问题

开了 HSTS 想关掉怎么办?

通过 HTTPS 发送 Strict-Transport-Security: max-age=0。浏览器下次看到这个头时才会忘记策略,不再访问的用户要等原来的 max-age 到期。已经预加载的域名还要在 hstspreload.org 申请移除,进入浏览器正式版本需要几个月。

includeSubDomains 安全吗?

只有所有子域名都能提供有效 HTTPS 时才安全,包括 oa.example.com 这类内网系统、自带 Web 管理界面的设备。加之前用 CT 证书查询 和 DNS 记录盘点一遍子域名。

宝塔面板怎么加 HSTS?

宝塔网站设置的 SSL 页面在部分版本里有 HSTS 开关(以面板实际界面为准);没有开关时,在网站的“配置文件”里 443 的 server 块中加 add_header Strict-Transport-Security "max-age=300" always;,保存后面板会自动重载 Nginx。

资料来源

  1. RFC 6797: HTTP Strict Transport Security (HSTS)
  2. HSTS Preload List Submission
  3. MDN: Strict-Transport-Security
  4. nginx: Module ngx_http_headers_module