运维实践
证书自动续期:在有效期缩到 47 天前搭好流水线
证书自动续期,就是让 ACME 客户端按计划获取新证书、部署到所有需要它的服务器、重载服务,并在任何一步失败时通知你。这已经不是可选项:公网 TLS 证书最长有效期从 2026 年 3 月 15 日起是 200 天,2029 年 3 月 15 日起只有 47 天,手工续期意味着每张证书每年要做十来次。
为什么手动续期行不通
47 天证书按 2/3 寿命续期,每年约要续期 11.6 次,手工流程跟不上。SC-081v3 投票 规定的公网 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 个月左右(以控制台说明为准)。完整日期见 证书有效期时间线,背后的原因见 47 天证书。
续期流水线的四个环节
只有当客户端看到新证书,续期才算完成。多数事故出在签发之后的环节,而不是签发本身。
+-----------+ +-----------+ +-----------+ +-----------+
| 获取 | --> | 分发 | --> | 重载 | --> | 验证 |
| 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(RFC 9773)允许 CA 为每张证书公布续期窗口,并在计划批量吊销时把窗口提前。Let's Encrypt 集成指南给出三条规则:
- 每张证书每天至少查两次 ARI,在建议窗口内续期。
- 拿不到 ARI 时,在剩余 1/3 寿命时续期;有效期不足 10 天的证书在一半时续期。
- 在一天中的随机时间运行,不要卡在整点;失败后退避重试: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 天,对短期证书来说永远来不及。
# 看看有没有装定时器
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:
# 默认 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:
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。
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 检测,一批域名用 批量 SSL 检测。
- 按剩余比例告警,不按固定天数:剩余不足 1/3 寿命时提醒,不足 1/6 时紧急告警。对 47 天证书来说,固定的“30 天前提醒”几乎在签发后就会触发。
- 监控续期任务本身:记录客户端的退出码或最近一次成功时间。
- 关注证书透明度:CT 日志记录了为你的域名签发的每一张证书,既能发现被遗忘的主机,也能发现意外签发,见 证书透明度。
上线前先演练
从没完整跑过的续期流水线只是一种猜测。趁当前证书还有充足余量时演练:
- 每次改动防火墙、安全组、负载均衡规则或 DNS 密钥后,都对测试环境跑一遍,例如
certbot renew --dry-run。 - 选一个低风险域名强制真实续期一次,确认部署钩子执行了、每个节点都重载了、外部检查看到了新的序列号。
- 用短期证书做“金丝雀”:在测试域名上用 6 天证书,每周把整条流水线跑两次左右,哪一步坏了几天内就会暴露。
- 故意弄坏一个环节,比如停掉金丝雀主机上的续期定时器,确认告警真的能送到人手里。
故障排查清单
大多数续期失败出自下面几种原因,按顺序排查:
| 现象 | 可能原因 | 处理 |
|---|---|---|
| Timeout during connect | 80 端口被防火墙或云安全组挡住,或未备案被拦截 | 放行 80 端口或改用 dns-01,见 certbot 超时 |
| 挑战 URL 返回 404 | 请求落到了别的服务器,或跳转规则破坏了路径 | 把 /.well-known/acme-challenge/ 路由到签发主机 |
| CAA 错误 | CAA 记录里没有你的 CA | 修改记录,见 CAA 记录 |
| 触发频率限制 | 反复失败或重复下单 | 先在测试环境修好,见 频率限制 |
| 磁盘上是新证书,线上还是旧证书 | 服务没重载,或某个节点没更新 | 加部署钩子,逐个节点检查 |
| 续期后证书链报错 | 只部署了叶子证书,没带中间证书 | 部署 fullchain.pem,见 缺少中间证书 |
| 私钥与证书不匹配 | 私钥和证书来自不同批次 | 两个文件一起部署,见 私钥不匹配 |
常见问题
续期任务多久跑一次合适?
每天至少两次,时间随机到分钟。没有证书到期时任务几乎不耗资源,频繁运行成本很低,而且支持 ARI 的客户端能在 CA 提前续期窗口后几小时内作出反应。
有了续期定时任务就够了吗?
不够。续期成功了,服务却可能还在用旧证书:没人重载服务,或者负载均衡后面的另一台机器根本没收到新文件。要加一个部署钩子负责重载,再加一个外部检查,在线上证书剩余寿命不足约 1/3 时告警。
47 天证书该设多少天续期?
在寿命约 2/3 处续期,也就是到期前 15~16 天左右,并让 ARI 在 CA 需要时把时间提前。写死“到期前 30 天”或“每 60 天”的脚本都会出问题。
阿里云、腾讯云买的证书也能自动续期吗?
要看签发方式。如果背后的 CA 提供 ACME 接口和 EAB 凭据,可以用同一套客户端自动化;云厂商控制台的证书一般通过其“证书托管/自动部署”类功能或 OpenAPI 更新到 CDN、负载均衡等产品,具体以产品文档为准。纯手工下载上传的方式在 47 天时代不可持续。