# 开启 HSTS：Nginx、Apache、宝塔、IIS 安全配置步骤

> 分步添加 Strict-Transport-Security 响应头：从很短的 max-age 开始，再加 includeSubDomains，满足 preload 预加载要求，避免把用户锁在门外。

Source: https://ssl.sh/zh/guides/enable-hsts/
Last updated: 2026-11-10

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

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

## 前提条件

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

- 证书有效且已配置自动续期（见 [证书自动续期](https://ssl.sh/zh/guides/renew-certificates-automatically/)）。
- 已经配置 HTTP 跳转 HTTPS（见 [HTTP 跳转 HTTPS](https://ssl.sh/zh/guides/redirect-http-to-https/)）。
- 没有混合内容；打算加 `includeSubDomains` 的，所有子域名都已支持 HTTPS。

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

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

**Nginx**，写在 `listen 443 ssl` 的 server 块里：

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

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

**Apache**，写在 `*:443` 虚拟主机里（需要 `a2enmod headers`）：

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

**Caddy**，写在站点块里：

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

**IIS**，写在网站的 `web.config` 里：

```xml
<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 年逐级提高，每一级观察几天没问题再往上调：

```text
max-age=300          （5 分钟）
max-age=86400        （1 天）
max-age=604800       （1 周）
max-age=31536000     （1 年）
max-age=63072000     （2 年）
```

## 第三步：加上 includeSubDomains

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

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

## 第四步（可选）：申请预加载

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

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

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

## 验证

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

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

[HSTS 检查](https://ssl.sh/zh/tools/hsts-checker/) 还会列出预加载条件是否满足。再用 [SSL 证书检查](https://ssl.sh/zh/tools/ssl-checker/) 确认背后的证书，或在终端运行：

```bash
curl ssl.sh/example.com
```

## 自动续期

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

## 常见问题排查

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