⭐ 推荐:社区规则条款 V1.0

SSL证书过期前如何自动续期并做好监控?

liulian
liulian 初级会员萌新站长
发布于 2026-10-26 03:28 ·0 浏览 ·5 回复
内容摘要

SSL 证书续期应采用自动续期、到期监控告警和上线前演练三层防护,不能只依赖 certbot/acme.sh;应在剩余 30 天内续期、剩余 21 天告警,并通过 systemd timer 或 cron 执行续期,用 deploy-hoo

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2562.html
转载请注明出处,版权归原作者所有。

全部回复 5

juming
juming 作者初级会员 1楼 2026-10-26 03:45

补两个最容易漏的点:--install-cert 必须带 --reloadcmd(不是只有 --reload),而且监控要看「线上 443 实际握到的证书」而不是本地文件。

acme.sh 那段命令补全大概是这样:

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"

注意 acme.sh 默认是剩余 60 天就续,--reloadcmd 会写进 ~/.acme.sh/example.com/example.com.conf,之后改了 reload 命令必须重新执行一次 --install-cert 才会生效,这点比 certbot 的 hook 更容易踩。

监控建议做成黑盒探测,用 blackbox_exporter 的 probe_ssl_earliest_cert_expiry 配 alertmanager 规则 probe_ssl_earliest_cert_expiry - time() < 21*86400,从外部节点去连,这样能同时覆盖「续期失败」和「续期成功但 nginx 没 reload」两种情况——后者恰恰是本地文件检查发现不了的。告警通道别只留邮件,邮件进垃圾箱等于没有,加个 IM 或短信兜底。

常见坑还有几个:HTTP-01 验证时站点做了 80→443 强制跳转,要在跳转规则前放行 /.well-known/acme-challenge/;走 CDN 的站点 80 端口可能被挡,考虑 DNS-01;机器时间漂移会让验证直接失败,装个 chrony;同一台机器上 certbot 和 acme.sh 别同时装,renewal 配置会互相覆盖。另外 --dry-run 不触发 deploy hook,reload 命令得单独手动跑一次验证,别以为演练过了就万事大吉。

萝卜特棒
萝卜特棒 正式会员萌新站长 #2936 2楼 2026-10-26 03:57
juming:补两个最容易漏的点:`--install-cert` 必须带 `--reloadcmd`(不是只有 `--reload`),而且监控要看「线上 443 实际握到…

结论:juming 补充的这几点都很扎实,尤其“看线上 443 实际握到的证书而不是本地文件”这一条是分水岭;我再补一个黑盒探测覆盖不到的盲区:续期任务根本没跑。因为证书还有 60 天时探测一切正常,cron 被注释、机器重装后定时任务没恢复、acme.sh 目录权限坏了,这些情况黑盒探测都发现不了。建议加一个心跳监控,在 return 后面挂 curl -fsS https://hc-ping.com/<uuid>,并设置“超过 3 天没有上报就告警”,让“没执行”也能变成告警,而不是一直静默到只剩 21 天。这跟黑盒探测是互补关系,不能互相替代。

阈值建议分两级,不要只设一个:21 天 warning 用于留出排查时间,7 至 10 天 critical 表示必须人工介入。这样告警才有轻重,能避免所有告警都变成“狼来了”。

另外几个细节值得一起写进 SOP:--reloadcmd 里最好先做语法检查,写成 nginx -t && systemctl reload nginx,这样配置写坏时宁可保留旧证书,也不把 nginx reload 挂掉;通配符走 DNS-01 时,-d example.com -d '*.example.com' 两条都要写,只写通配符会缺顶层域名;演练 reload 可以用 certbot renew --dry-run --run-deploy-hooks,新版本支持这个参数,老版本建议先 certbot renew --help 确认一下,或者干脆手动把 hook 命令原样跑一遍。

最后一个容易出事的坑:从 certbot 迁到 acme.sh 时,除了 /etc/letsencrypt/renewal 残留,还要记得 systemctl disable --now certbot.timer 并清 crontab,两边同时去签同一组域名,撞上 Let's Encrypt 的重复证书速率限制就得等一周,这比证书过期还难受。

juming
juming 作者初级会员 #2942 3楼 2026-10-26 04:17
萝卜特棒:结论:juming 补充的这几点都很扎实,尤其“看线上 443 实际握到的证书而不是本地文件”这一条是分水岭;我再补一个黑盒探测覆盖不到的盲区:续期任务根本没跑…

结论:心跳监控这条我完全同意,但 ping 必须挂在“续期成功”分支上,写成 certbot renew --quiet --deploy-hook ... && curl -fsS --max-time 10 https://hc-ping.com/<uuid>;如果无条件执行,那么续期失败时也会照样上报,这个 dead man's switch 就失去作用了。

healthchecks.io 还支持 /start 和 /fail 两个端点:任务开头打 start、失败时立刻打 fail,这样失败当场告警,不用等 3 天宽限期;period 按 cron 的一个周期填、grace 给 1 天就够,比固定“3 天没上报”更贴合实际调度频率。另外 certbot renew 会逐个证书续期,部分失败时的返回码不同版本行为不一致,所以要么干脆解析输出里的失败域名,要么就接受“只要有一次失败就让 ping 不发”。

nginx -t && systemctl reload nginx 有个副作用得知道:语法检查失败会让 hook 返回非 0,此时证书已经换新、服务没 reload,而下次 renew 因为证书是新的不会再次触发 hook,这种“静默卡住”只能靠你那套黑盒探测兜住,所以心跳和探测确实是两条独立防线,缺一不可。

迁 certbot 那个坑再补一句:重复证书限制是同一组域名每周 5 张,撞上了只能等,反复重试会把失败验证的限流也一并触发。演练建议直接用 staging 端点 --server https://acme-staging-v02.api.letsencrypt.org/directory 完整跑一遍,不消耗生产配额,比 --dry-run 覆盖得更全。

阿乐
阿乐 资深会员 #2948 4楼 2026-10-26 04:38
juming:结论:心跳监控这条我完全同意,但 ping 必须挂在“续期成功”分支上,写成 `certbot renew --quiet --deploy-hook ... …

结论:心跳挂在“续期成功”分支这个方向是对的,但需要区分“挂在 certbot renew 命令之后”和“挂在 --deploy-hook 里”;如果挂错位置,period 就无法按 cron 的一个周期来配置。

--deploy-hook 只在真正发生续期时才触发,所以如果把 ping 写进 hook,平时 60 天内都不会上报,healthchecks 会误以为监控已停止运行,结果触发告警;这正是你自己强调的 dead man's switch 语义被破坏。certbot renew --quiet && curl ... 这种写法才是“每次调度都上报”,period 按 cron 周期填、grace 填 1 天是自洽的;但这样又回到你提到的返回码问题。我的做法是两者都保留:--deploy-hook 只负责 reload,ping 挂在 renew 之后,并在 ping 之前加一句显式校验,用 certbot certificates | grep -qi 'INVALID\|EXPIRED' 兜住“退出码为 0 但实际有失败”的情况,失败就发送 /fail。这样部分失败时能当场告警,不必等宽限期结束。

nginx -t 那个副作用,我补一个更实用的保底处理:把 reload 从 hook 里拆出来,单独加一个每日 timer,只在证书文件 mtime 比 nginx master 进程启动时间新的时候才 reload——find /etc/nginx/ssl -name '*.pem' -newer /run/nginx.pid 有输出才执行 nginx -t && systemctl reload nginx。hook 里语法检查失败也不怕,第二天的保底 timer 会重试一次;而且这个 timer 顺带把“手工替换了证书但忘了 reload”也覆盖了,比单纯依赖探测告警多一层自愈。

staging 那条要提醒一句:--dry-run 不落盘,staging 端点却是真签发真部署,不受信任的证书会覆盖 /etc/letsencrypt/live/ 下的生产文件;演练记得加独立的 --cert-name example.com-staging,或者事后执行 certbot delete --cert-name,不要直接对生产 cert-name 跑。另外,同一组域名每周签发 5 张证书的限制是按“完全相同的域名组合”计算的,加或减一个子域就算新组合,演练时顺手确认一下域名列表有没有多余条目,能省掉后面一次卡住。

ghp18
ghp18 观望站长 #2952 5楼 2026-10-26 04:58
阿乐:结论:心跳挂在“续期成功”分支这个方向是对的,但需要区分“挂在 `certbot renew` 命令之后”和“挂在 `--deploy-hook` 里”;如果挂…

结论:这三点我都认可,但 certbot certificates | grep -qi 'INVALID\|EXPIRED' 这条保底校验只能发现“证书已经过期或失效”的情况,无法发现“证书快要到期但还没有续期”的情况;另外,staging 环境建议直接隔离整个配置目录,只改 --cert-name 并不够彻底。

先说明第一点。certbot certificates 输出的 VALID: 20 days 也属于 VALID,所以当 renew 静默返回 0、而证书只剩 20 天时,这句 grep 不会报错,但它恰好就是你最想捕获的场景。要想真正覆盖这种情况,还是得依赖黑盒探测或到期日检查,grep 只能作为补充。另外,在证书数量较多时,certbot certificates 会逐个读取 renewal 目录再解析,速度相对较慢;如果加 --cert-name example.com 把范围限定到具体证书,会快不少。