证书自动续期成功了,浏览器却还在报警:Nginx 没 reload 的老坑
ACME 自动续期后证书文件已更新,但 Nginx 工作进程仍持有旧证书,导致浏览器持续报证书过期。本文分析这一常见故障的根因、排查方法和可靠的处置方式,包括 reload 钩子和自动化部署的衔接。
本文目录

一个典型现场
半夜告警:证书还有 3 小时过期。登上机器一看,Certbot 明明三天前就续期成功了,/etc/letsencrypt/live/ 下的证书有效期是新的一轮。但 openssl s_client -connect 打过去,返回的还是旧证书,到期时间纹丝不动。
这不是 ACME 出问题,也不是浏览器缓存。原因朴素得让人不好意思:Nginx 没有 reload,worker 进程内存里还拿着启动时加载的旧证书。
为什么证书换了,服务发的还是旧的
Nginx 处理 TLS 握手时用的是 master 进程加载进内存、再随 fork 传给 worker 的证书和私钥。磁盘上文件变了,它不会自己去重读。必须显式触发一次配置重载:
nginx -s reload
# 或
systemctl reload nginxreload 的逻辑是 master 重新读配置、拉起新 worker、旧 worker 处理完手头连接后退出。这一步之后才轮到新证书上岗。证书签发工具和 Web 服务器之间的这一脚,是很多"自动化"方案里最容易断掉的一环——签发是自动的,生效靠运气。

还有几种相似的变体:
- HAProxy、Envoy、某些自研网关同理,进程内持有证书,换了文件不热加载就白搭。
- 多服务共用一套证书:Nginx reload 了,但旁边还有个 mail 服务或内网网关挂着同一份证书,漏了其中一个,告警照样响。
- 多机负载均衡:签发只在一台机器上完成,其他节点没同步,用户命中哪台全看运气,故障表现为"时而报警时而不报",最难排查。
排查套路
遇到"证书已续期但仍报过期",按这个顺序查,基本几分钟能定位:
- 直接看服务端实际发什么证书,别信浏览器:
`` echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates ``
- 对比磁盘上证书的日期:

`` openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates ``
- 两者日期不一致 → 进程没 reload;日期一致但浏览器仍报错 → 问题在别处(比如 CDN 边缘节点还缓存着旧证书,或链不完整)。
- 有多台机器的,逐台打一遍第 1 条。LB 后面藏着一台没同步的旧节点是经典剧情。
顺手提醒一个相关但不相同的坑:如果只 reload 了证书文件本身、而签发工具换了目录路径(比如换了域名导致 live/ 下的目录名变了),reload 会因为找不到文件而失败,Nginx 继续用旧配置跑。nginx -t 先于 reload 是个好习惯。
让续期真正"闭环"
处置思路只有一个:把"签完证书"和"让服务用上证书"绑成一个原子动作,中间不留人工环节。
Certbot 自带 deploy hook,续期成功且证书确有更新时才执行:
certbot renew --deploy-hook "systemctl reload nginx"
注意它只在实际换了证书时触发,不会每次检查都 reload,这点设计得挺体贴。
在 CertbotX 中实践
手动维护 deploy hook 在多机、多入口的场景下很快会变成一堆散落各处的脚本。在 CertbotX 里,这条链路是连起来的:ACME 自动签发(含 DNS-01 的 CNAME 委托,方便把验证域名交给专门区域)、到期前自动重新签发,然后直接向 Agent、EdgeOne 或 CDN 推送部署,部署动作本身有记录可审计。也就是说"签发—部署—确认生效"在一个流程里完成,排查时可以从部署记录直接看到某台节点是否真正换上了新证书,而不是靠 SSH 逐台去猜。
最后两个边界情况
- CDN / 边缘节点证书独立:源站证书换了,CDN 上配置的是另一份证书,用户看到的以 CDN 为准。排障时先确认流量链路里哪一环在终止 TLS。
- HSTS 下的证书错误没有"继续访问"按钮:一旦证书出问题且站点开了 HSTS,用户被完全挡在门外,故障影响面会陡然放大。这也是证书自动化必须做扎实的一个理由,而不是不做 HSTS 的理由。
---