# 证书自动续期：在有效期缩到 47 天前搭好流水线

> 证书有效期降到 200、100、47 天后，手动续期必然出错。何时续期、ARI 怎么用，certbot、acme.sh、DNS API、宝塔与云厂商托管怎么配，多台服务器怎么分发和监控。

Source: https://ssl.sh/zh/learn/automate-certificate-renewal/
Last updated: 2026-11-10

证书自动续期，就是让 ACME 客户端按计划获取新证书、部署到所有需要它的服务器、重载服务，并在任何一步失败时通知你。这已经不是可选项：公网 TLS 证书最长有效期从 2026 年 3 月 15 日起是 200 天，2029 年 3 月 15 日起只有 47 天，手工续期意味着每张证书每年要做十来次。

## 为什么手动续期行不通

47 天证书按 2/3 寿命续期，每年约要续期 11.6 次，手工流程跟不上。[SC-081v3 投票](https://ssl.sh/zh/ballots/sc-081/) 规定的公网 TLS 证书最长有效期如下：

| 签发时间 | 最长有效期 | 按 2/3 寿命续期，每年次数 | 域名验证复用期 |
|---|---|---|---|
| 2026-03-15 之前 | 398 天 | 约 1.4 次 | 398 天 |
| 2026-03-15 起 | 200 天 | 约 2.7 次 | 200 天 |
| 2027-03-15 起 | 100 天 | 约 5.5 次 | 100 天 |
| 2029-03-15 起 | 47 天 | 约 11.6 次 | 10 天 |

最后一列和第一列同样重要。到 2029 年，CA 只能复用 10 天内的域名验证结果，几乎每次续期都要重新做一次挑战。“运维把文件发给同事、同事再传到网站根目录”这种流程根本跟不上。

Let's Encrypt 比规则走得更快：可选的 `tlsserver` 配置从 2026 年 5 月 13 日起签发 45 天证书；默认的 `classic` 配置在 2027 年 2 月 10 日从 90 天改为 64 天，2028 年 2 月 16 日改为 45 天；`shortlived` 配置现在就签发 160 小时的证书。国内云厂商的免费证书也已缩短到 3 个月左右（以控制台说明为准）。完整日期见 [证书有效期时间线](https://ssl.sh/zh/timeline/certificate-validity/)，背后的原因见 [47 天证书](https://ssl.sh/zh/learn/47-day-certificates/)。

## 续期流水线的四个环节

只有当客户端看到新证书，续期才算完成。多数事故出在签发之后的环节，而不是签发本身。

```text
  +-----------+     +-----------+     +-----------+     +-----------+
  |   获取    | --> |   分发    | --> |   重载    | --> |   验证    |
  | ACME 订单 |     | 复制到每个|     | nginx、   |     | 从外部检查|
  | + CSR     |     | 节点、SLB、|     | HAProxy、 |     | 线上实际  |
  |           |     | CDN       |     | Java 应用 |     | 出示的证书|
  +-----------+     +-----------+     +-----------+     +-----+-----+
        ^                                                     |
        +------------------- 失败告警 ------------------------+
```

- **获取**：ACME 客户端完成挑战，下载新证书链。
- **分发**：证书和私钥送到每个终止 TLS 的地方：其他 Web 节点、负载均衡、CDN、邮件服务器、硬件设备。
- **重载**：每个进程加载新文件。nginx、HAProxy 需要 reload；很多 Java、Node.js 应用需要重启或重新加载 keystore。
- **验证**：从网络外部连上去，检查实际出示的到期时间和证书链。

## 何时续期：ARI 优先，1/3 兜底

最好的续期时间是 CA 建议的时间。[ACME Renewal Information](https://ssl.sh/zh/learn/acme-protocol/)（RFC 9773）允许 CA 为每张证书公布续期窗口，并在计划批量吊销时把窗口提前。Let's Encrypt 集成指南给出三条规则：

1. 每张证书每天至少查两次 ARI，在建议窗口内续期。
2. 拿不到 ARI 时，在剩余 1/3 寿命时续期；有效期不足 10 天的证书在一半时续期。
3. 在一天中的随机时间运行，不要卡在整点；失败后退避重试：1 分钟、10 分钟、100 分钟，之后每天一次。

对 90 天证书，兜底规则意味着到期前 30 天续期；对 45 天证书则是到期前约 15 天。Let's Encrypt 的公告特别提醒：写死“每 60 天续期”的做法将不再可行，并建议在 cron、包装脚本和运维文档里搜一搜 60、80、83 这类数字。

## Certbot：定时器与部署钩子

大多数发行版安装 Certbot 时会带一个 systemd 定时器或 cron，每天运行两次 `certbot renew`。Certbot 4.0 起，证书剩余寿命不足 1/3 即视为到期；4.0 之前是固定的 30 天，对短期证书来说永远来不及。

```bash
# 看看有没有装定时器
systemctl list-timers | grep certbot

# 用测试环境完整演练一遍续期
sudo certbot renew --dry-run

# 只在续期成功后重载 nginx
sudo certbot renew --deploy-hook "systemctl reload nginx"
```

部署钩子只在证书真正续期后执行，而 `--pre-hook`、`--post-hook` 每次尝试都会执行。也可以把可执行脚本放进 `/etc/letsencrypt/renewal-hooks/deploy/`，对所有证书生效，不用改每个续期配置。Certbot 会给部署钩子传 `RENEWED_LINEAGE`、`RENEWED_DOMAINS` 环境变量，足够用来把文件复制到其他主机。

## acme.sh：cron、DNS API 与 install-cert

acme.sh 安装时会加一个每日 cron，并在自己的目录里保存证书副本。关键一步是 `--install-cert`：它记住要把文件复制到哪里、每次续期后执行什么命令。国内用户最常见的是配合 DNSPod 或阿里云解析做 dns-01：

```bash
# 默认 CA 不是 Let's Encrypt，需要的话先切换
acme.sh --set-default-ca --server letsencrypt

# DNSPod（腾讯云 DNS 解析）的 API Token
export DP_Id="123456"
export DP_Key="your-dnspod-token"
acme.sh --issue --dns dns_dp -d example.com -d '*.example.com'

# 阿里云解析则用 dns_ali，配合最小权限 RAM 子账号的 AccessKey
# export Ali_Key="..." ; export Ali_Secret="..."
# acme.sh --issue --dns dns_ali -d example.com -d '*.example.com'

acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.fullchain.pem \
  --reloadcmd      "systemctl reload nginx"
```

不要让 nginx 直接引用 acme.sh 工作目录里的文件，内部目录结构可能随版本变化。API 密钥会被 acme.sh 保存到 `account.conf`，所以务必使用只能改 DNS 记录的子账号。

## 宝塔面板与云厂商的自动化

宝塔面板的 SSL 页面申请 Let's Encrypt 证书时，会同时添加一个计划任务负责续签。它省事，但有两个常见坑：计划任务被误删或执行失败时，面板不会主动告诉你；站点开启了强制 HTTPS、防盗链或 CDN 回源规则后，`/.well-known/acme-challenge/` 访问不到，http-01 会失败，这时改用面板里的 DNS 验证方式。

证书部署在阿里云、腾讯云的 CDN、负载均衡（SLB/CLB）、WAF 上时，手工在控制台上传新证书最容易遗漏。两家云都提供把证书一键部署到云产品、并在证书更新后同步的功能，也提供 OpenAPI，可以在 ACME 部署钩子里调用（具体能力以产品文档为准）。无论用哪种方式，都要在外部检查每个接入点实际出示的证书。

## 自带 ACME 的服务器

最简单的流水线是服务器自己续期自己的证书。Caddy 和 Traefik 默认自动签发和续期，Apache httpd 有 `mod_md`，nginx 在 2025 年 8 月推出了官方的 `ngx_http_acme_module`：

```nginx
resolver 223.5.5.5;   # 模块需要解析器来访问 ACME 服务器

acme_issuer letsencrypt {
    uri https://acme-v02.api.letsencrypt.org/directory;
    state_path /var/cache/nginx/acme-letsencrypt;
    accept_terms_of_service;
}
acme_shared_zone zone=ngx_acme_shared:1M;

server {
    listen 443 ssl;
    server_name example.com;
    acme_certificate letsencrypt;
    ssl_certificate       $acme_certificate;
    ssl_certificate_key   $acme_certificate_key;
    ssl_certificate_cache max=2;
}

server {
    listen 80;           # http-01 挑战在这里应答
    location / { return 404; }   # 挑战请求由模块自行处理
}
```

内置 ACME 能省掉单机的分发和重载环节，但外部监控仍然不能少。

## dns-01 自动化与密钥权限

通配符、内网主机和多机集群适合用 dns-01，但它把 DNS 密钥带进了签发流程。三种方法可以缩小风险：

- 如果 DNS 服务商支持，使用只能修改某个域名 TXT 记录的受限 Token 或 RAM 子账号。
- 用 CNAME 委托验证，例如 `_acme-challenge.example.com CNAME example.com.acme.example.net.`，让密钥只能控制一个专门的“验证区”。
- 在一台专用主机上集中签发，再把证书推送到 Web 服务器，而不是让每台 Web 服务器都保存 DNS 密钥。

## 多台服务器与负载均衡

服务器不止一台时，先决定由哪台机器负责签发。每个节点各自跑客户端，会造成重复下单、触发频率限制，而且 CA 的 http-01 请求可能落到没有 token 的节点上。常见做法：

| 模式 | 做法 | 注意 |
|---|---|---|
| 集中签发 | 一台主机用 dns-01 续期，再通过 Ansible、rsync 或密钥管理服务分发 | 推送失败、私钥分发安全 |
| 共享挑战路径 | 负载均衡把 `/.well-known/acme-challenge/` 转发到同一台节点 | 规则要经得起配置变更 |
| 在边缘终止 | 负载均衡或 CDN 自己申请证书 | 回源证书仍需续期 |
| Kubernetes | cert-manager 把证书写进 Secret，供 Ingress 使用 | Ingress 控制器要能重新加载 Secret |

## Kubernetes 与 cert-manager

在 Kubernetes 里，cert-manager 是自动化证书的标准方案。`Certificate` 资源写明域名和 `Issuer`，cert-manager 默认在证书寿命过去 2/3 时续期，结果写入 Secret。

```yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
spec:
  secretName: example-com-tls
  dnsNames: ["example.com", "www.example.com"]
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
  renewBeforePercentage: 33
```

优先用 `renewBeforePercentage` 而不是固定的 `renewBefore`：CA 缩短有效期后，固定时长可能超过新证书的寿命，导致反复续期，cert-manager 文档专门提醒过这一点。

## 监控与告警

监控能把静悄悄的续期失败变成一张工单，而不是一次故障。Let's Encrypt 在 2025 年 6 月 4 日停止发送到期邮件，理由是成本和保存数百万邮箱带来的隐私负担，所以你得自己盯。

- **从外部检查**：像真实客户端一样带 SNI 连接每个公网域名和端口。磁盘上的文件说明不了负载均衡实际出示的是什么。单个主机用 [SSL 检测](https://ssl.sh/zh/tools/ssl-checker/)，一批域名用 [批量 SSL 检测](https://ssl.sh/zh/tools/bulk-ssl-checker/)。
- **按剩余比例告警，不按固定天数**：剩余不足 1/3 寿命时提醒，不足 1/6 时紧急告警。对 47 天证书来说，固定的“30 天前提醒”几乎在签发后就会触发。
- **监控续期任务本身**：记录客户端的退出码或最近一次成功时间。
- **关注证书透明度**：CT 日志记录了为你的域名签发的每一张证书，既能发现被遗忘的主机，也能发现意外签发，见 [证书透明度](https://ssl.sh/zh/learn/certificate-transparency/)。

## 上线前先演练

从没完整跑过的续期流水线只是一种猜测。趁当前证书还有充足余量时演练：

- 每次改动防火墙、安全组、负载均衡规则或 DNS 密钥后，都对测试环境跑一遍，例如 `certbot renew --dry-run`。
- 选一个低风险域名强制真实续期一次，确认部署钩子执行了、每个节点都重载了、外部检查看到了新的序列号。
- 用短期证书做“金丝雀”：在测试域名上用 6 天证书，每周把整条流水线跑两次左右，哪一步坏了几天内就会暴露。
- 故意弄坏一个环节，比如停掉金丝雀主机上的续期定时器，确认告警真的能送到人手里。

## 故障排查清单

大多数续期失败出自下面几种原因，按顺序排查：

| 现象 | 可能原因 | 处理 |
|---|---|---|
| Timeout during connect | 80 端口被防火墙或云安全组挡住，或未备案被拦截 | 放行 80 端口或改用 dns-01，见 [certbot 超时](https://ssl.sh/zh/errors/certbot-timeout-during-connect/) |
| 挑战 URL 返回 404 | 请求落到了别的服务器，或跳转规则破坏了路径 | 把 `/.well-known/acme-challenge/` 路由到签发主机 |
| CAA 错误 | CAA 记录里没有你的 CA | 修改记录，见 [CAA 记录](https://ssl.sh/zh/learn/caa-records/) |
| 触发频率限制 | 反复失败或重复下单 | 先在测试环境修好，见 [频率限制](https://ssl.sh/zh/errors/lets-encrypt-rate-limit/) |
| 磁盘上是新证书，线上还是旧证书 | 服务没重载，或某个节点没更新 | 加部署钩子，逐个节点检查 |
| 续期后证书链报错 | 只部署了叶子证书，没带中间证书 | 部署 `fullchain.pem`，见 [缺少中间证书](https://ssl.sh/zh/errors/missing-intermediate-certificate/) |
| 私钥与证书不匹配 | 私钥和证书来自不同批次 | 两个文件一起部署，见 [私钥不匹配](https://ssl.sh/zh/errors/nginx-key-values-mismatch/) |

需要查看服务器到底出示了什么，可以参考 [连接测试命令手册](https://ssl.sh/zh/cookbook/test-connections/) 和 [故障排查命令手册](https://ssl.sh/zh/cookbook/troubleshooting/)。
