证书半夜过期告警:Let's Encrypt 自动续期为什么没跑,怎么查
线上证书到期但 certbot 没自动续上,多数不是签发失败而是续期链路某环断了。本文按从快到慢的顺序给出排查路径:确认续期器是否在跑、用 dry-run 复现、处理验证方式变更和部署未生效这几类常见断点。
本文目录

半夜收到证书过期告警,登录一看,certbot 明明装过、certbot renew 也写过 cron,证书却还是 90 天前那张。这种情况在运维现场很常见,而且多数不是 Let's Encrypt 那边出问题,而是续期这条链路上某一环悄悄断了。排查思路是:从"续期器到底跑没跑"开始,一环一环往下验证,每一步都能当场确认。
先看定时器,别先看证书
很多机器装完 certbot 后,真正负责续期的是 systemd timer,而不是你以为的 cron。两条都看一眼:
systemctl list-timers | grep certbot
crontab -l
如果 timer 状态是 inactive,或者 cron 里那行被谁注释掉了,答案到此为止。还有个隐蔽情况:机器是从旧镜像克隆的,timer 在,但 certbot.timer 指向的配置文件里 OnCalendar 被改过,或者机器长期关机错过了触发窗口——systemctl status certbot.timer 里的 Last 字段会告诉你上次实际触发时间。
用 dry-run 复现,而不是干等
定时器在跑,但证书没换,那就手动复现一次:
certbot renew --dry-run
这一步的信息量最大。常见输出对应的坑:
- HTTP-01 验证失败:80 端口被新的防火墙规则挡了,或者后来加的反向代理把
/.well-known/acme-challenge/拦截、重定向到 HTTPS 时路径丢了。站点"加了 HSTS / 全站跳转 HTTPS"之后续期开始失败,几乎全是这个原因。 - DNS-01 失败:DNS API 凭据过期、子账号权限被收,或者域名 NS 改过但插件配置没跟着改。
renewal配置残留:/etc/letsencrypt/renewal/里的 conf 记录了当初的验证方式和插件参数。域名从 A 验证换成通配符后,旧 conf 还是按 http 验证在跑,自然过不去。
dry-run 用的是 staging 环境,不会消耗正式签发配额,可以放心多跑几次。
签下来了,但服务还在用旧证书

另一种"看起来没续期":证书其实续上了,certbot certificates 显示有效期很新,但浏览器里还是老证书。这是因为续期和部署是两步。nginx 不会自己重读磁盘上的证书文件,要靠 renew 钩子:
/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh里面放一句 nginx -s reload(注意先 nginx -t 更稳妥)。如果这个钩子不存在、没执行权限、或者证书是 HAProxy/其他服务在用而钩子只 reload 了 nginx,就会出现"Let's Encrypt 那边一切正常,线上照旧过期"的怪象。证书续得勤快,服务睡得安稳,两不相干。
在 CertbotX 中实践

如果这类"定时器 + 钩子 + 凭据"的手工链路维护起来太碎,可以把链路收敛到一处管理。CertbotX 的 ACME 自动签发和到期前重新签发覆盖了续期这一环;通配符或主域 DNS 不在手边的场景,可以用 DNS-01 CNAME 委托把验证域名指到受控区域,避免动主 DNS 的权限。签发成功后,证书可以自动部署到 Agent、EdgeOne 或 CDN,不再依赖每台机器上各自写的 reload 钩子;每次部署都有记录可查,排查"证书换没换、换到哪台了"时不用挨个登机器翻日志。这套方式不消除上面任何一类故障,但把断点从分散的脚本挪到了一个能看记录的地方。
收尾建议
修好之后别急着关终端。把 certbot renew --dry-run 的成功输出留一份,确认续期配置里验证方式和当前架构一致;给证书到期监控留至少两周的提前量,别等 24 小时告警才处理——那时候签发端任何一点抖动都会直接变成事故。
参考核验: