Certbot退出码0但443端口证书未更新?Nginx证书续期排查指南

发布时间:2026/8/28 15:35:19
Certbot退出码0但443端口证书未更新?Nginx证书续期排查指南 在 Nginx Certbot 的 HTTPS 维护场景里最常见也最容易误判的一个问题就是Certbot 续期命令执行完毕退出码是 0表面看没有任何错误但当你用 OpenSSL 检查 443 端口时看到证书仍然停留在上个月的有效期范围。这个问题的麻烦之处在于“看起来成功”和“实际生效”之间隔着一层没有直接暴露的环节很多人会反复执行续期命令结果都是 exited 0问题却始终没有消失。在正式排查之前先说明一个前提如果你刚刚运行了certbot renew并且退出码是 0不要急着确认证书已更新。退出码 0 只代表 Certbot 自己的执行流程没有报错并不代表浏览器或客户端握手的最终结果已经变好。本文按“退出码含义 - 证书文件状态 - 443 端口内容 - 服务加载行为 - 代理与缓存”的顺序把整条链路理清最后给出可以直接照做的排查命令和确认清单。1. 先拆开“Certbot exited 0”退出码 0 不等于证书已经生效1.1 Certbot 的退出码描述的是“流程执行”不是“证书一定更新”很多运维同学看到exited 0的第一反应是“成功了”。在 systemd timer 或 crontab 的日志里Certbot exited 0确实说明 Certbot 进程执行完整个流程后正常退出没有抛异常。但“正常退出”只能说明本轮续期逻辑按预期走完了不能说明“证书被换成了新证书”。Certbot 的renew行为可以分成两种情况证书确实到期或即将到期Certbot 发起续期成功拿到新证书然后执行 deploy hook。证书距离到期还有较长时间Certbot 检查到期时间后直接跳过日志里出现Certificate not yet due for renewal此时同样返回退出码 0。所以退出码 0 是一个“流程状态码”而不是“证书更新状态码”。你需要通过certbot certificates或openssl x509去确认证书是否真的变了。1.2 先看 certbot certificates确认 Certbot 自己认为证书是什么状态在打开 OpenSSL 查 443 端口之前先确认 Certbot 当前记录的证书状态sudo certbot certificates这条命令会输出 Certbot 管理的所有证书关键字段包括Certificate Name证书配置名称通常是域名。Domains包含哪些域名。Expiry Date证书过期时间。Certificate Path当前 live 目录下的证书路径。如果你看到Expiry Date仍然对应上个月签发的证书那说明 Certbot 这轮续期根本没有成功或者根本没有触发续期。如果Expiry Date已经变成未来新日期但openssl s_client检查 443 端口仍是旧证书问题就转移到“服务没有读取新证书”这一层。这里要注意certbot certificates输出的证书路径通常指向/etc/letsencrypt/live/域名/fullchain.pem这是 Certbot 通过 symlink 指向 archive 目录的入口。只要 Certbot 认为证书已经更新这个路径下的 fullchain.pem 会跟着指向新的 archive 文件。1.3 记住 live 目录下的四份文件分别是什么/etc/letsencrypt/live/域名/目录下通常有四个文件/etc/letsencrypt/live/example.com/ ├── cert.pem ├── chain.pem ├── fullchain.pem └── privkey.pem它们之间的关系是cert.pem只包含站点证书不含中间证书。chain.pem只包含中间证书链。fullchain.pem站点证书 中间证书链Nginx 的ssl_certificate一般用它。privkey.pem私钥Nginx 的ssl_certificate_key用它。排查时一定要确认服务配置里用的是fullchain.pem还是cert.pem。如果配置写的是cert.pem在部分客户端上会出现证书链不完整的问题同时也会让你在openssl s_client输出里看到与预期不同的颁发链。虽然这不会直接导致“443 显示旧证书”但它会影响你对证书的比对判断。注意不要只验证/etc/letsencrypt/live/下文件时间对不对还要确认 Nginx 配置真正指向的是哪一个文件。文件时间只能说明文件被替换过不能说明服务已经读进来。2. OpenSSL 看到旧证书之前先弄清楚你在检查哪一层的证书2.1 三个容易混淆的对象磁盘文件、服务内存、客户端握手排查“443 端口仍显示旧证书”时容易把下面三个对象混在一起磁盘上的证书文件例如/etc/letsencrypt/live/example.com/fullchain.pem。正在运行的服务通常是 Nginx加载到内存中的证书。客户端通过 TCP 443 端口做 TLS 握手时拿到的证书。Certbot 续期只负责更新第 1 项。第 2 项需要 Web 服务重载或重启才会更新。第 3 项是客户端真实得到的证书也是 OpenSSL 命令能看到的证书。你要查的问题出在第 3 项但原因可能在 1、2 之间断开了。如果第 1 项已经是新证书第 3 项还是旧证书说明第 2 项没有跟上。如果第 1 项还是旧证书问题出在 Certbot 续期本身和第 3 项没有关系。2.2 用 openssl s_client 抓取 443 端口当前真实证书检查客户端实际拿到的证书不需要重新下载 OpenSSL直接使用系统自带的openssl命令即可echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2/dev/null | openssl x509 -noout -subject -dates -issuer解释一下参数s_clientopenssl 提供的 TLS 客户端工具用于向指定地址发起握手。-connect 127.0.0.1:443连接本机 443 端口也可以换成公网 IP 或域名。-servername example.com通过 SNI 指定访问的域名。如果 443 端口上配置了多个证书这个参数会直接决定你拿到哪份证书。| openssl x509 -noout -subject -dates从握手结果中提取证书的主体、生效时间和过期时间。输出大致是这样subjectCN example.com notBeforeJan 5 00:00:00 2025 GMT notAfterApr 5 00:00:00 2025 GMT issuerC US, O Lets Encrypt, CN R11如果你当前已经处于 3 月而notBefore还是 1 月 5 日说明端口呈现的不是 3 月续期后的证书。如果notAfter距当前不足 30 天也说明旧证书仍在线。同时检查磁盘上当前 live 目录的证书时间sudo openssl x509 -enddate -subject -noout -in /etc/letsencrypt/live/example.com/fullchain.pem如果这条命令输出的notAfter是新日期而openssl s_client输出的notAfter是旧日期就可以确定磁盘已经更新服务没有读取。2.3 检查系统时间证书时间线对比的前提证书的notBefore和notAfter都是绝对时间。判断“上个月”和“新证书”必须依赖当前系统时间。如果服务器时间慢了几天或快了几天很容易误导判断。检查系统时间date timedatectl如果发现系统时间与真实时间偏差过大先同步时间再重新做证书时间对比。时间同步本身也可能导致 Certbot 对“是否到期”的判断发生偏差但这种情况在正常使用 NTP 同步的服务器上很少见。注意openssl 命令读证书本地时间读的是服务器系统时间浏览器读到的证书有效期则是客户端系统时间和证书时间的对比。同一份证书在两边显示可能不同但证书本身的notBefore、notAfter字段是确定的不会因为系统时间改变而改变。3. 根因一证书文件已经更新但 Nginx 没有重新读取3.1 Nginx 只在启动或 reload 时读取证书文件这是最常见的根因很多人对 Certbot 续期的理解是证书文件被替换后Nginx 会自动感知。实际上 Nginx 不会自动重新读取证书。Nginx worker 进程在启动或执行 reload 时才会重新加载ssl_certificate和ssl_certificate_key指向的文件。用 openssl 命令直接看磁盘文件看到的是新证书用openssl s_client连接 443 端口看到的是旧证书。这种差异就是典型的“文件更新了但服务没有 reload”。3.2 对比服务中的证书时间和磁盘上的证书时间把第 2 节的两条命令放在一起执行echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2/dev/null | openssl x509 -noout -enddate sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem如果第一条输出的是旧时间第二条输出的是新时间说明 Nginx 还持有旧证书。此时执行 reloadsudo systemctl reload nginxreload 完成后再次执行第一条命令。如果输出已经变成新证书的时间说明问题就是服务没有重新读取证书文件。3.3 reload 与 restart 的选择以及 deploy-hook 的作用Nginx 的reload是平滑重载master 进程重新读取配置启动新的 worker旧的 worker 处理完当前连接后退出。这个过程不会断开正在进行的请求所以 Certbot 的自动化续期一般推荐使用 reload。restart会强制停止再启动虽然也能读取新证书但会造成连接中断不适合生产环境自动执行。Certbot 在续期成功后是否会执行 reload取决于两个条件使用的插件是否自带了 reload 逻辑。是否配置了 deploy hook。certbot renew如果使用--nginx插件部分版本会在续期成功后自动 reload Nginx如果使用--webroot或--certonly则不会自动 reload。deploy hook 是一个更通用的方式在续期成功且安装新证书后执行sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy sudo tee /etc/letsencrypt/renewal-hooks/deploy/nginx-reload /dev/null EOF #!/bin/bash systemctl reload nginx EOF sudo chmod x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload脚本里的systemctl reload nginx会在 Certbot 成功续期后触发。如果 reload 失败需要确认脚本权限、systemd 服务名称和 Nginx 配置是否正确。如果你发现 Certbot 已经配置了 deploy hook但 443 端口仍是旧证书先手动执行一遍 hook 脚本再检查脚本是否真的被调用sudo bash -x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload3.4 现象、检查和修复速查项目典型现象检查方式处理建议磁盘证书fullchain.pem 时间已经是新时间用 openssl x509 读文件说明续期已完成不需要再次续期端口证书openssl s_client 输出仍是旧时间连接 443 端口看握手结果执行 reload 或重启 TLS 终止服务deploy hook日志显示 hook 未执行或执行失败查看 letsencrypt.log 和 hook 脚本权限手动执行 hook 脚本修复脚本错误多 worker / 多实例部分 IP 是新证书部分 IP 是旧证书从不同网络或机器检查 443检查每一台机器上的证书分发和 reload4. 根因二Certbot 没有续期只是按规则跳过了4.1 日志中的 not due for renewal 不是错误Certbot exited 0还有一种非常常见的情况Certbot 检查完证书到期时间后认为证书还不需要续期直接跳过。此时日志会出现类似下面这样的内容Certificate not yet due for renewal No renewals were attempted.这种日志不代表异常。Lets Encrypt 证书有效期是 90 天Certbot 默认会在证书剩余时间不足 30 天时触发续期。如果你的域名证书上个月刚签发现在运行certbot renew大概率就是返回 0 但没有任何替换动作。如果你误以为“退出码 0 就是续期成功”可能根本不会去看日志然后花很长时间排查一个本来就没有发生的问题。4.2 看清 renew 日志里到底发生了什么查看 Certbot 日志确认本轮执行是否有真正的续期动作sudo tail -100 /var/log/letsencrypt/letsencrypt.log关注几个关键字Skipped证书未到期跳过。Renewing an existing certificate正在续期。Successfully received certificate成功拿到新证书。Running deploy-hook command正在执行部署钩子。Error出现错误。如果日志里是Skipped那exited 0只是“顺利跳过”的意思。此时不需要改 Nginx也不需要 reload因为根本没有新的证书文件产生。4.3 强制续期只能在明确需要时使用生产环境要克制如果确实需要立即换新证书比如怀疑旧证书私钥泄露或证书内容有问题可以强制续期sudo certbot renew --force-renewal--force-renewal会让 Certbot 无视剩余时间要求重新申请证书。但这里要非常克制频繁向 Lets Encrypt 发起续期请求可能触发频率限制生产环境也不要把它写进 cron 定时任务里。--force-renewal执行完成后再运行certbot certificates确认Expiry Date已经变化然后手动 reload Nginx 或确认 deploy hook 已经执行。这里经常出现第三个坑强制续期确实产生了新证书但没有配置 deploy hookNginx 还是旧内存证书。这又回到第 3 节的问题。日志关键字含义下一步动作Skipped证书未到期不续期不需要 reload保留现有服务Renewing an existing certificate进入续期流程等待续期完成关注是否成功Successfully received certificate新证书已写入 live 目录确认 deploy hook 是否执行 reloadRunning deploy-hook command续期后正在执行部署钩子检查 hook 输出和最终端口证书5. 根因三443 端口背后不是你想的那份服务配置5.1 导出 Nginx 真实配置找到所有 server 块如果磁盘证书已经更新reload 也已经执行但 443 端口仍是旧证书下一个要怀疑的是443 端口走的根本不是你以为的那个 Nginx server 块。先导出 Nginx 当前生效的全部配置sudo nginx -T 2/dev/null | grep -E server_name|ssl_certificate|ssl_certificate_key|listennginx -T会把所有配置解析后的内容输出包含 include 进来的所有文件。listen 443 ssl;表示该 server 块监听 443ssl_certificate表示它实际使用的证书路径。如果配置里出现了多个ssl_certificate指向不同路径要确认访问example.com时命中哪个 server 块。域名匹配顺序是精确匹配 通配符前缀匹配 正则匹配 默认 server。如果你配置了一个默认 server 或正则 server客户端访问可能落到旧证书所在的 server 块。如果想看到某个域名最终命中了哪个证书可以在配置里临时关闭默认 server 的监听或直接通过 SNI 抓包确认。更简单的做法是echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2/dev/null | openssl x509 -noout -subject如果 subject 不是CN example.com说明命中了别的证书大概率是默认 server 配置。5.2 live 目录 symlink 被手动改过导致新证书没有接上Certbot 会自动把/etc/letsencrypt/live/域名/下的文件用 symlink 指向/etc/letsencrypt/archive/域名/下最新的版本。如果之前有人手动把fullchain.pem换成普通文件或手动改过 symlink 指向Certbot 续期后可能不会自动更新这个链接。检查 live 目录里的文件类型ls -la /etc/letsencrypt/live/example.com/正常情况是fullchain.pem - ../../archive/example.com/fullchain1.pem privkey.pem - ../../archive/example.com/privkey1.pem如果fullchain.pem是普通文件续期时 Certbot 可能不会覆盖它。此时需要重新建立 symlinksudo ln -sf /etc/letsencrypt/archive/example.com/fullchain1.pem /etc/letsencrypt/live/example.com/fullchain.pem sudo ln -sf /etc/letsencrypt/archive/example.com/privkey1.pem /etc/letsencrypt/live/example.com/privkey.pem sudo systemctl reload nginx注意archive目录下的文件名会随着续期次数增加例如fullchain1.pem、fullchain2.pem。调整 symlink 前要确认当前最新的 archive 文件编号。5.3 如果不是 Nginx 在监听 443先定位 TLS 终止点有时候 443 端口根本不是 Nginx 在监听。可能是 haproxy、Traefik、Apache、Java 网关或者是 Docker 容器里的另一套服务。先确认监听进程sudo ss -ltnp | grep :443输出里会出现 PID 和进程名。如果监听进程不是 Nginx比如是haproxy或java那么 reload Nginx 没有意义。你需要 reload 或重启真正持有 TLS 证书的服务进程。如果本机 443 端口没有输出说明流量可能经过四层负载均衡在更前面的设备上终止了 TLS。这时用本机 IP 检查 443 端口没有意义应该用对外的域名、负载均衡地址或真实客户端网络来检查。5.4 多实例、负载均衡和证书分发场景下端口背后可能不是本机如果环境里有多个 Nginx 实例证书可能只在其中一台机器上执行了续期。其他机器的证书还是旧文件。这就是为什么从不同网络检查同一个域名有时会得到不同的证书时间。排查时不要只看当前机器。建议把检查命令放到负载均衡后面的每一台后端机器上执行或者直接访问域名对比多次握手结果。证书分发场景下还要检查分发任务是否执行成功比如 ansible、rsync、对象存储同步等。检查对象命令判断标准本机 443 监听进程sudo ss -ltnp | grep :443确认 TLS 终止点是否在本机本机 Nginx 全量配置sudo nginx -T 2/dev/null | grep -E server_name|ssl_certificate确认 server 块和证书路径live 目录 symlinkls -la /etc/letsencrypt/live/example.com/确认指向 archive 最新文件多台后端机器在每台机器上执行端口证书检查确认每台机器都拿到新证书负载均衡或公网地址从外部机器用域名检查 443确认客户端视角的最终结果6. 根因四OCSP stapling、代理缓存与部署钩子的隐性影响6.1 OCSP stapling 可能让客户端拿到旧签名时间Nginx 开启了ssl_stapling on;后会向 OCSP 服务器查询证书状态并把查询结果缓存。续期后如果没有 reloadNginx 可能继续发送旧的 OCSP staple 响应。客户端看到的是旧证书或者至少是旧证书对应的状态信息看起来就像“443 端口还是旧证书”。检查是否开启了 OCSP staplingsudo nginx -T 2/dev/null | grep -E ssl_stapling|ssl_trusted_certificate如果配置里有ssl_stapling on;确认ssl_trusted_certificate是否指向正确的 chain 文件。正确配置 OCSP stapling 需要完整的证书链否则 stapling 不生效Nginx 只会向客户端发送空 staple。在 openssl 客户端里显式请求 stapling 响应echo | openssl s_client -connect 127.0.0.1:443 -servername example.com -status 2/dev/null | grep -A 10 OCSP response如果OCSP Response Status: successful对应的时间还是旧的说明 Nginx 还在使用旧 staple。reload Nginx 或关闭 stapling 后再重新开启可以刷新这个状态。6.2 deploy-hook 执行失败或未触发证书没有 reload有些环境里 Certbot 是自动化续期的但续期后没有触发任何 reload因为 deploy hook 目录为空或者 hook 脚本写错了服务名。比如脚本里写的是systemctl restart nginx但实际服务名是openresty、nginx2或容器名reload 执行后仍然失败。查看上次续期后的日志确认有没有执行 hooksudo grep -iE deploy-hook|renewal-hooks /var/log/letsencrypt/letsencrypt.log | tail -50如果日志里根本没有Running deploy-hook command说明 Certbot 没有触发 hook。最常见原因是续期实际没有发生或 hook 没有配置到 renewal 配置中。Certbot 支持三种 hook 目录目录触发时机/etc/letsencrypt/renewal-hooks/pre/续期开始前/etc/letsencrypt/renewal-hooks/deploy/成功续期并安装新证书后/etc/letsencrypt/renewal-hooks/post/每次续期尝试结束后deploy hook 脚本必须可执行否则 Certbot 会跳过或报错。检查权限sudo ls -l /etc/letsencrypt/renewal-hooks/deploy/脚本需要包含 shebang并且权限至少是755。6.3 其它 TLS 终止层和应用层缓存不能忽略如果 443 端口后面不是 Nginx而是 Java 网关、haproxy、Tomcat、Jetty 或自定义 Go 程序那么“reload 服务”这个动作需要换成各自对应的方式haproxy需要systemctl reload haproxy且 haproxy 2.x 对证书加载和 reload 的处理与 Nginx 不同。Java 应用证书路径可能配置在server.ssl.certificate必须重启应用或动态刷新密钥库。自定义 Go 程序需要代码实现了tls.LoadX509KeyPair且支持热更新否则只能重启进程。Docker/Nginx 容器需要重新创建容器或进入容器 reload 后确认挂载的证书文件路径是否与宿主机一致。这类问题最典型的特征是本地磁盘证书是新的本机没有任何 Nginx443 端口也正常但客户端就是看到旧证书。原因就是 TLS 终止点在应用层应用层并没有重新读取磁盘证书。注意浏览器缓存不会导致openssl s_client看到旧证书因为 openssl 每次执行都建立新的 TLS 连接。如果 openssl 看到旧证书说明服务端确实在提供旧证书而不是客户端缓存问题。7. 从现象到根因的四层排查链路7.1 第一层对比文件证书和端口证书按下面的顺序执行先确定“磁盘新不新”和“端口新不新”的差异。# 1. 查看当前 Certbot 记录的证书信息 sudo certbot certificates # 2. 查看磁盘 live 目录中证书的过期时间 sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem # 3. 查看 443 端口当前握手得到的证书过期时间 echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2/dev/null | openssl x509 -noout -enddate根据结果分支磁盘证书端口证书初步判断新新证书已经生效不需要继续排查新旧服务没有重新读取证书向下走 7.2旧旧Certbot 可能没有续期回到第 4 节旧新很少见但可能是手动替换过证书文件或配置指向了其他路径7.2 第二层根据差异选方向缩小到“没续期”还是“没加载”如果磁盘证书是旧的先看 Certbot 日志sudo tail -100 /var/log/letsencrypt/letsencrypt.log关注有没有Skipped、Renewing、Successfully received certificate。如果之前根本没有触发过续期运行一次前台续期sudo certbot renew --force-renewal --dry-run--dry-run不会真正覆盖线上证书只会验证续期流程能否走通。确认流程没问题后再根据实际情况考虑是否使用--force-renewal。如果磁盘证书是新的端口证书是旧的直接进入服务加载层。检查监听进程和 reloadsudo ss -ltnp | grep :443 sudo systemctl reload nginxreload 后立刻重新检查端口证书。7.3 第三层检查监听进程、配置文件和 hook确认监听进程是不是 Nginxsudo ss -ltnp | grep :443如果不是 Nginx去 reload 对应的服务。如果是 Nginx再看配置sudo nginx -T 2/dev/null | grep -E server_name|ssl_certificate|listen确认example.com命中的 server 块里ssl_certificate指向的路径确实是/etc/letsencrypt/live/example.com/fullchain.pem。然后检查 deploy hook 是否生效sudo ls -l /etc/letsencrypt/renewal-hooks/deploy/ sudo tail -50 /var/log/letsencrypt/letsencrypt.log如果 hook 脚本存在但一直没有执行先手动执行再观察日志。7.4 第四层确认客户端视角和监控告警本机排查完成后还要从外部客户端视角确认echo | openssl s_client -connect example.com:443 -servername example.com 2/dev/null | openssl x509 -noout -dates如果本机是新证书外部域名仍是旧证书需要怀疑负载均衡、CDN 节点缓存证书、或证书没有分发到多台后端机器。生产环境里不要只信一次检查结果可以多试几个网络节点。如果你在管理一批证书最好配置一个证书剩余时间监控脚本或 Prometheus exporter以notAfter减去当前时间的结果作为告警指标。证书续期是否成功最终应该由监控系统把关而不是靠人肉检查日志。8. 生产环境如何把“续期成功”变成“真正生效”8.1 学习环境复现和临时修复的做法学习环境想复现这个问题最快的方法是准备一台带域名的测试服务器安装 Certbot 和 Nginx。先用 webroot 或 HTTP-01 方式签发一张证书。手动修改/etc/letsencrypt/live/example.com/fullchain.pem和privkey.pem或者用certbot renew --force-renewal生成新证书。不执行 reload直接运行echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2/dev/null | openssl x509 -noout -dates此时你会看到端口上的证书时间还没变这就是“证书文件更新但服务没有 reload”的现场。然后在另一台机器上观察certbot certificates和/var/log/letsencrypt/letsencrypt.log可以直观理解退出码 0 和证书生效之间的区别。临时修复手段如果确认证书文件已经更新执行sudo systemctl reload nginx。如果确认 Certbot 只是跳过可以执行sudo certbot renew --force-renewal。如果 live 目录 symlink 坏了重建 symlink 后再 reload。如果 443 端口背后不是 Nginxreload 对应进程。8.2 用 deploy-hook 固定 reload 动作避免手工遗漏生产环境里不要依赖手工 reload。推荐把 deploy hook 配置好让 Certbot 在每次成功续期后自动完成 reload。脚本示例#!/bin/bash if [ -n $RENEWED_DOMAINS ]; then systemctl reload nginx fi脚本放在/etc/letsencrypt/renewal-hooks/deploy/nginx-reload并赋予执行权限sudo chmod x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload如果使用 systemd timer 或 cron 自动续期需要确认定时任务执行用户有权限执行 reload。通常用 root 运行 Certbot 续期任务deploy hook 也能以 root 身份执行。如果环境里用的是 Apache把脚本里的 reload 命令换成systemctl reload apache2 # 或 systemctl reload httpd8.3 三个值得反复记住的坑第一个坑把exited 0当成了“证书已经更新成功”。实际判断标准应该是openssl s_client握手得到的notAfter而不是 Certbot 的退出码。第二个坑强制续期后不重载 Nginx。certbot renew --force-renewal会生成新证书文件但如果没配 deploy hook端口可能仍服务旧证书。看到Successfully received certificate后还要确认 reload 是否发生。第三个坑在负载均衡或多实例环境里只检查本机。如果证书没有同步到其他机器只有部分入口是新证书客户端从不同网络访问会得到不同结果。续期、分发、reload 三步必须按机器逐一确认。8.4 每次续期后可以照做的检查清单检查项命令或方式确认结果Certbot 记录状态sudo certbot certificatesExpiry Date 为新日期磁盘证书时间sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem与期待的新时间一致端口证书时间echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2/dev/null | openssl x509 -noout -dates与磁盘证书时间一致监听进程sudo ss -ltnp | grep :443确认 TLS 终止点reload 动作sudo systemctl reload nginx或 deploy hook命令退出码为 0且端口证书时间变化日志sudo tail -50 /var/log/letsencrypt/letsencrypt.log没有 Error续期或跳过原因清晰外部视角从其他网络执行 openssl s_client公网入口的新证书时间一致监控证书剩余天数告警告警已恢复或即将到期时间正确这套清单可以贴在每次证书续期排障的文档里。下次再遇到 “Certbot exited 0OpenSSL 检查 443 还是旧证书” 的问题不要再重复执行续期命令先跑一遍清单确定是 Certbot 没续期、Nginx 没 reload、配置指错路径还是端口背后根本不是 Nginx。找到差异点之后针对那一个环节做处理问题就能在十分钟内定位。