证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘

一次典型的 TLS 故障复盘:证书按时续签,告警却照样响。根因不在签发,而在证书落盘后服务没有重载,以及节点间部署不同步。记录排查路径和后续的预防做法。

本文目录
证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘

事情发生在周一早上。监控告警说某个域名证书还有三天过期,但运维同事翻了一下记录:证书明明上周就自动续签成功了,新文件就躺在 /etc/letsencrypt/live/ 下面,时间戳很新。签发系统没问题,出问题的是签发之后的那段路。

症状先确认清楚

第一反应是怀疑监控误报,所以直接在线上敲:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:DNS ownership verification using a delegated CNAME, shown as a precise handoff between two controlled zones
证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:DNS ownership verification using a delegated CNAME, shown as a precise handoff between two controlled zones查看原图

返回的 notAfter 确实只剩三天。再对比本地磁盘上的证书文件:

openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates

磁盘上是新证书,线上服务给出去的却是旧的。到这里,问题范围已经从"证书签发"缩小到"证书生效"。这是个很重要的分界——很多人排查时会一头扎进 ACME 日志,其实方向从一开始就偏了。

根因:重载没发生

证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:an ACME request being validated at separate stations and turned into a new deployable artifact while private key material remains at its origin
证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:an ACME request being validated at separate stations and turned into a new deployable artifact while private key material remains at its origin查看原图

这台机器跑的是 nginx。TLS 证书是进程启动或 reload 时读进内存的,磁盘文件换了,nginx 自己不会察觉。自动化脚本用 certbot 续签,renewal 配置里缺了 --deploy-hook(或者 hook 存在但某次机器迁移后丢了),于是整个流程变成了:证书续签成功 → 文件更新 → 没有 nginx -s reload → 老证书继续服务到期。

排查时有两个细节值得记下:

  1. certbot renew --dry-run 是成功的。这很误导人——dry-run 只验证签发链路,不会真的执行 deploy hook 后的完整生效路径,至少不能假设它覆盖了你线上的部署动作。
  2. systemctl reload nginx 如果配在 post hook 里,要注意 hook 只在实际发生续签时执行,平时 renew 命令静默退出,hook 不会跑。这本身没错,但容易让人误以为"hook 一直在工作"。

更麻烦的是第二层问题:这个域名后面还有两台边缘节点,证书是靠 rsync 定时同步过去的。主节点证书更新了,同步任务因为一次目录结构调整静默失败,边缘节点还在用旧证书。所以即使主节点 reload 了,部分用户依然会打到旧证书上——这正是为什么有人刷新页面看到的证书"时好时坏"。证书问题一旦出现"偶发",优先怀疑多节点不同步,而不是怀疑浏览器缓存。

修复和预防

证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:a new certificate version moving from its source to several edge appliances while the previous working version remains ready for rollback
证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:a new certificate version moving from its source to several edge appliances while the previous working version remains ready for rollback查看原图

当天的修复很朴素:手动 reload nginx,修好同步脚本,逐台节点用上面的 s_client 命令验证一遍。事后做了三件事:

  • 续签流程里显式配置 deploy hook,重载动作和证书更新绑定在同一条链路上,不依赖人肉记得。
  • 监控不只盯"证书快过期",还加了"线上证书序列号是否等于磁盘最新证书序列号"这类一致性检查。过期告警只能告诉你结果,一致性检查能在用户受影响前几天就发现部署断了。
  • 把多节点的证书同步从定时拉取改成续签后主动推送,成功失败都有记录可查。

在 CertbotX 中实践

这类"签好了没生效"的故障,CertbotX 的处理思路是把签发和部署做成一条流水线:ACME 自动签发和到期前重新签发之后,证书会直接推送到 Agent、EdgeOne 或 CDN 侧,不依赖运维手动同步或补 hook。每次部署都有记录,可以回查哪台目标在什么时间换上了哪张证书——复盘时不用再靠翻 rsync 日志拼时间线。如果域名走 DNS-01 验证,CNAME 委托也能把 _acme-challenge 的处理隔离开,签发环节本身基本不需要人介入。

证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:an approaching certificate expiry being detected early, followed by a fresh request and a safe replacement before the live connection is interrupted
证书明明续签成功,线上却还是过期了:一次 deploy hook 漏配的复盘:an approaching certificate expiry being detected early, followed by a fresh request and a safe replacement before the live connection is interrupted查看原图

工具能消除的是"步骤被遗忘"这类问题,但线上证书与实际部署一致性这一点,仍建议保留自己的监控做兜底。自动化和人各看一层,比单层信任都可靠。

一点收尾的判断

证书故障里,签发失败的反而是少数,多数是"签发成功了但后面断了":没 reload、没同步、负载均衡后面有漏网的节点。排查顺序也应该是倒着来的——先看线上在发什么证书,再对比磁盘,最后才看签发日志。从用户实际看到的东西往回推,比从流程开头往后顺推快得多。

延伸阅读,供核验:

← 返回资讯中心