十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

服务器迁移后Let‘s Encrypt证书自动续期失效的排查与修复

服务器迁移后Let‘s Encrypt证书自动续期失效的排查与修复 1. 迁移后的故障现场该续的没续该换的没换1.1 浏览器先炸还是服务先炸两种典型报错形态服务器迁移这件事最折磨人的不是迁移当天而是迁移后的一到两周。迁移当天所有服务看起来都是通的SSH能连Nginx能起网站能开你以为万事大吉。结果过了几天浏览器开始弹大红叉NET::ERR_CERT_DATE_INVALID紧接着用户开始反馈网站不安全打开是拦截页。我第一次在迁移后遇到Lets Encrypt证书问题就是这个套路。当时我把整套业务从旧机器平移到新机器Nginx配置、数据库、静态文件都搬完了唯独下意识觉得证书这东西有自动续期不用管。结果就是这种自动续期的错觉让我在迁移后的第十天被线上事故打脸。这类故障通常分两种形态很多人一开始分不清。第一种是浏览器直接报证书过期。这种情况其实反而好排查坏得明显。你打开https://example.com浏览器明确告诉你此网站的安全证书已过期或者证书无效错误码可能是NET::ERR_CERT_DATE_INVALID也可能是ERR_CERT_AUTHORITY_INVALID。前者是时间问题后者是信任链问题。时间问题大概率是证书文件本身就过期了或者服务器系统时间不准信任链问题则可能牵扯到根证书、中间证书没配全。第二种更隐蔽服务一切正常但证书的到期时间保持在迁移前的旧日期上。你用一个第三方SSL检测工具去查发现证书剩余天数越来越少但Nginx没有任何报错。因为你新机器上跑的其实是一份从旧机器拷过来的、已经过期的证书文件Nginx照样加载浏览器照样拦截但Web服务进程自己完全无感。这就像门锁已经锈死了但业主还不知道每天照样假装锁了门。1.2 为什么迁移后一周才暴露问题这里有个时间差的学问。Lets Encrypt证书有效期是90天certbot默认的策略是剩余时间不足30天才真正触发续期。所以哪怕旧服务器已经停止续期动作了只要证书还有31天以上有效期一切看起来风平浪静。等你搬完服务器慢慢做完DNS切换、数据校验、配置调整一两个星期过去了证书剩余天数跌破30天此时新机器上的续期机制没接上问题正式爆发。也就是说迁移后你大概有一个窗口期在这个窗口里不处理证书迁移系统的隐患不会立刻爆炸。但它一定会爆炸只是早晚问题。这也就解释了为什么那么多人在迁移半个月后才开始排查证书问题——因为在第14天之前检测工具显示证书还没过期等它真过期了一切都已经来不及优雅处理。2. 先搞清楚一件事自动续期实际是谁在干活2.1 自动续期不是Lets Encrypt干的是你的本地客户端干的很多人的误解根源在于Lets Encrypt证书会自动续期这句话。准确地说Lets Encrypt这个CA证书颁发机构只负责签发和吊销证书它不会追着你家的服务器说哎你证书快到期了我帮你换一个。签发完证书那一刻CA和你的服务器就再无瓜葛了续期完全是你服务器上那个客户端程序的责任。社区里最常见的客户端是certbot也有acme.sh、lego这类工具。以certbot为例安装时它会往系统里注册一套定时任务。Debian/Ubuntu系通常是systemd的certbot.timerCentOS 7系可能直接写在crontab里具体看版本和安装方式。这套定时任务才是自动续期的真正执行者。所以当你问Let‘s Encrypt证书到底会不会自动续期时准确答案是如果你安装certbot时正确注册了定时任务并且证书目录、配置文件、验证端口都保持正常那么它确实能全自动续期一旦其中任何一个环节被破坏——比如服务器迁移——这个自动就名存实亡。2.2 certbot续期的三个硬性前提certbot的续期逻辑看似简单但触发成功需要满足几个前提缺一个就失败。第一个是定时任务必须活着。你可以用systemctl status certbot.timer看状态用systemctl list-timers | grep certbot看它下次触发时间。如果这条链路断了certbot这辈子都不会主动跑一次。第二个是证书目录必须完整。certbot续期不是凭空去CA那边拿一张新证书它要基于本地的/etc/letsencrypt目录里的账户信息、域名配置、私钥文件来发起申请。目录结构大概是/etc/letsencrypt/ ├── accounts/ # ACME账户密钥 ├── archive/ # 历史证书归档 ├── live/ # 当前生效证书的软链接 └── renewal/ # 每个域名独立的续期配置文件renewal/里每个*.conf文件记录了这个域名用哪种验证方式、证书路径在哪、需要包含哪些域名。迁移时如果你只拷贝了Nginx配置没拷/etc/letsencrypt整个目录那certbot即使跑起来也找不到任何可以续期的证书。第三个是验证通道必须通畅。Lets Encrypt续期和首次签发一样需要证明你确实拥有这个域名。HTTP-01挑战会通过你的域名去访问一个临时路径例如http://你的域名/.well-known/acme-challenge/xxxx。这意味着你的域名DNS必须指向当前服务器而且80端口必须能正常响应这个路径。新服务器上如果没装Nginx或者Nginx配置里没把80端口监听起来或者防火墙拦了80端口验证就会失败续期自然失败。2.3 续期阈值还剩30天才是换证时机certbot每次触发定时任务后并不会无脑给所有证书续期。它的逻辑是检查证书剩余有效期只有少于30天时才会真正动手。所以你在日志里经常看到这样的内容Certbot is not due for renewal of certificate example.com yet这句不是报错是正常的跳过。Lets Encrypt把证书有效期定成90天加上30天续期阈值等于每张证书在生命周期内大概会续期两次到三次。这样设计有两个用意一是短效证书即使私钥泄露影响面也有限二是强迫使用者把续期做成自动化不能靠人手去记到期时间。理解了这个机制你就能明白为什么迁移后不要干等自动续期如果旧证书还剩下35天而新服务器的定时任务没接上certbot确实不会主动做任何事因为还没到30天临界点。但如果你不去检查永远不会知道系统在静默地等一个永远等不到的续期动作。这也是这类故障最可怕的地方。3. 服务器迁移动了哪些续期命脉3.1 systemd服务状态跟着老系统一起归零服务器迁移不是简单的文件复制它本质上是把你原来那套运行环境在另一台机器上重新搭建一遍。而certbot的续期能力恰恰是绑定在运行环境上的。最常见的丢失项是certbot.timer和certbot.service的systemd单元文件。很多人在迁移时备份了网站目录、数据库、Nginx配置但很少有人会想到备份systemd的服务定义。到了新机器上你重新安装了certbot却忘了systemctl enable certbot.timer于是定时器根本没有启用。更隐蔽的一种情况是新机器的系统版本和旧机器不一致。比如旧机器是Ubuntu 18.04新机器是Rocky Linux 9或者反过来。不同发行版里certbot的安装路径、插件机制、默认定时器命名都有差异。Ubuntu 18.04的certbot可能自带certbot.timer而某些从EPEL装的certbot则默认只放/etc/cron.d/certbot。你把旧机器的运维习惯带到新机器就会漏看这些差异。3.2 证书目录没搬全等于白搬迁移时的第二个重灾区就是/etc/letsencrypt目录。我见过不少同行迁移时只把Nginx配置里的ssl_certificate和ssl_certificate_key指向的.pem文件拷过去完全没管/etc/letsencrypt。这种做法短期看没问题Nginx能启动HTTPS能访问。但问题在于renewal/配置目录是空的certbot不知道有哪些证书需要管理也不会去更新live/下的软链接。等证书真过期了你只能手动重新签发而且由于账户密钥没迁移新签发的证书和你原来那批证书在ACME账户维度上就断开了关系。正确做法是把整个/etc/letsencrypt目录打包带走最好连/var/log/letsencrypt的日志目录一起。这样certbot的账户、证书、续期配置全部原样恢复新机器上跑certbot renew --dry-run就能无缝衔接。3.3 DNS切换、IP更换与验证时机的错位这个坑在云上迁移时尤其容易踩。很多迁移流程是先把新服务器所有服务配好然后改DNS解析让它指向新IP最后再处理证书。问题就出在时间顺序上。如果你在DNS还没切换之前就尝试跑certbot renewACME验证服务器访问你的域名时请求会打到旧服务器IP上。此时旧服务器可能已经关了或者旧服务器的HTTP服务已经停了验证自然失败。反过来如果你切了DNS之后跑续期但新服务器的80端口被其他服务占用、或者安全组没放行80端口同样会失败。所以迁移过程中的证书操作应该严格放在DNS已切换、新服务器防火墙已放行、80端口已有HTTP服务响应这三个条件全部满足之后。顺序反了你会在排查时被这些变量绕晕。3.4 时间同步问题一个容易被忽略的伪证书过期新装好的服务器如果没配置NTP或者chrony系统时间可能是错的。有时候云镜像里刻的时间还是几个月前有时候时区没设对导致本地时间和UTC差了好几个小时。证书校验对时间极其敏感。客户端和服务器比对证书有效期时都基于各自本地的系统时间。服务器时间慢了15天一张本来还剩20天有效的证书会被客户端判定为只剩5天进而触发不必要的告警服务器时间快了10天一张还有效的证书可能直接显示过期。排查证书问题时第一件事永远是先date看一眼服务器时间确认时区和实际时间都正确。这个习惯能帮你省掉大量无意义的证书检查。4. 从证书报错反向定位一次完整的排查链路4.1 第一步用一行命令确认证书真实到期时间不管浏览器报什么错第一步永远是先看服务器上的证书文件实际到期时间。使用openssl直接读最干净利落openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem输出里会有notAfter字段格式类似Sep 12 12:00:00 2025 GMT。拿到这个时间立刻就能判断问题是证书确实过期了还是系统时间错乱。如果你不确定Nginx实际加载的是哪个证书文件先看nginx.conf里ssl_certificate指向的路径再跟着路径读文件。常见误区是看了一个与实际情况无关的证书文件比如自己手动拷贝的/etc/ssl/example.com.pem结果真正生效的却是/etc/letsencrypt/live/下的软链接指向的那份。4.2 第二步手动跑一次续期让错误直接暴露排查续期问题最有效的手段就是手动触发续期不经过定时任务直接在前台跑一次把所有错误输出尽收眼底certbot renew --dry-run--dry-run是试运行不会真正换证书但会完整走一遍ACME验证流程用来确认整条链路是否通畅。如果这条命令能顺利跑完并显示Congratulations说明验证通道、证书目录、ACME账户全部正常问题大概率出在定时任务上如果报错错误信息会直接告诉你问题在哪报Domain not found或No valid IP address found说明DNS解析有问题或者域名没指向当前机器。报Connection refused或Timeout during connect说明80端口不通检查防火墙和安全组。报The certificate failed to validate说明HTTP-01验证时ACME服务器没能访问到.well-known/acme-challenge/路径检查Nginx是否配置了对应location路由。报Could not find a usable config或者Unrecognized arguments说明renewal配置目录有问题或者certbot版本和插件不匹配。4.3 第三步从日志里找定时任务的执行痕迹手动跑完不出错那就把怀疑对象转向定时任务本身。先看timer状态systemctl list-timers | grep certbot如果有输出说明timer已注册如果没有说明定时器根本不存在或者没启用。再看上一次执行日志journalctl -u certbot.service -n 50也可以直接翻开certbot自己的日志文件/var/log/letsencrypt/letsencrypt.log搜索最近的Certbot is not due for renewal或者Congratulations字样。如果日志里连最近一两周的执行记录都没有那就坐实了定时任务压根没跑的判断。4.4 修复重建续期链路并恢复certbot管理权根据排查结果修复方案基本是固定的几个动作。先恢复证书目录。如果你迁移时带了/etc/letsencrypt备份直接解压覆盖回去tar -xzpf letsencrypt-backup.tar.gz -C /覆盖之后用certbot certificates确认certbot能看到证书列表并且路径和Nginx配置引用一致。然后恢复定时任务。Debian系certbot包一般自带certbot.timer但可能需要手动启用systemctl enable --now certbot.timer systemctl status certbot.timer如果你用的是CentOS系certbot的cron脚本可能位于/etc/cron.d/certbot查看里面的内容确认cron配置存在即可。接着重新拉一次证书确保当前文件不是旧的certbot renew这条命令只有在证书剩余天数低于30天时才会真正换新。如果剩余时间还多可以用--force-renewal强制续期不过日常不建议没事就强制续能用--dry-run验证链路就足够了。最后重载Web服务让新证书生效nginx -t nginx -s reload4.5 踩坑备注迁移时别只拷证书文件这次事故之后我每次迁移服务器都会特别注意一个点证书迁移不只是搬运文件更是搬运管理状态。/etc/letsencrypt目录里不仅有.pem证书还有ACME账户密钥、renewal配置、archive历史归档。这三样东西缺一样certbot的自动化管理能力就打折扣。我现在的迁移习惯是连/etc/letsencrypt、/var/log/letsencrypt、/etc/systemd/system/certbot.*一起打包备份最多再带一份crontab -l的输出。宁可多带不可少带。因为在新环境里重建这些东西虽然不难但排查过程消耗的时间成本远比多打一个压缩包要多得多。5. 验证和长效巡检别等浏览器报错才想起证书5.1 用dry-run作为迁移后的必检动作迁移完成后我建议把certbot renew --dry-run当作和测试数据库连接同级别的验收步骤。它不需要等证书临近过期不管剩余天数多少都能完整走一遍验证流程是最快确认续期链路正常的办法。整个迁移Checklist做下来大概长这样检查项命令预期结果证书文件是否完整ls -l /etc/letsencrypt/live/域名/存在fullchain.pem和privkey.pem证书到期时间openssl x509 -enddate -noout -in /etc/letsencrypt/live/域名/fullchain.pem到期时间在未来30天以上certbot能否识别证书certbot certificates域名列表和实际一致定时任务是否注册systemctl list-timers | grep certbot显示下次触发时间续期链路是否通畅certbot renew --dry-run显示Congratulations或成功提示Nginx配置是否引用正确nginx -T | grep ssl_certificate路径指向/etc/letsencrypt/live/系统时间是否准确date与实际时间一致时区正确5.2 一个简单的剩余天数检查脚本除了certbot自己的dry-run我还会在服务器上放一个简单的巡检脚本每周跑一次检查所有证书的剩余天数。脚本逻辑很直接用openssl读到期时间转成时间戳减去当前时间戳得到剩余天数低于阈值就告警。#!/bin/bash CERT_DIR/etc/letsencrypt/live THRESHOLD40 for dir in $CERT_DIR/*/; do CERT$dir/fullchain.pem if [ ! -f $CERT ]; then continue fi EXPIRE_DATE$(openssl x509 -enddate -noout -in $CERT | cut -d -f2) EXPIRE_TS$(date -d $EXPIRE_DATE %s) NOW_TS$(date %s) REMAIN_DAYS$(( (EXPIRE_TS - NOW_TS) / 86400 )) DOMAIN$(basename $dir) echo $DOMAIN 剩余 $REMAIN_DAYS 天 if [ $REMAIN_DAYS -lt $THRESHOLD ]; then echo $DOMAIN 将在 $REMAIN_DAYS 天后过期请及时处理 # 这里可以接告警比如curl一个Webhook fi done阈值我习惯设40天。因为还剩30天是certbot的续期触发点我在40天就告警给自己留出10天的缓冲时间去排查为什么certbot没成功续期。这个缓冲在线上环境里非常重要它把紧急事故变成了常规工单。5.3 云厂商免费证书和Lets Encrypt的区别要分清排查过程中还有一个容易混淆的点就是你手头的证书到底是哪来的。阿里云、腾讯云这类云厂商也提供免费SSL证书很多站点历史上混用过几家的证书。它们的续期逻辑完全不同云厂商的免费证书通常有效期1年不提供自动续期你需要在控制台手动申请新证书再重新部署到服务器上。所以当你发现服务器上明明有自动续期任务证书还是过期了的时候先确认一下证书链的签发者是谁。用openssl看一下openssl x509 -issuer -noout -in /etc/letsencrypt/live/example.com/fullchain.pem如果签发者是Lets Encrypt那就是certbot的职责范围如果是其他CA别指望certbot来管老老实实按云厂商的流程去手动换证。我见过太多人拿着Lets Encrypt的排查思路去查云厂商证书查半天毫无结果。6. 几个容易忽略的连带问题和运维细节6.1 老版本JDK和ISRG根证书的兼容性聊到Lets Encrypt证书有一类问题不发生在服务器上而发生在客户端。这正好是近期很多人搜ISRG Root X1的原因。Lets Encrypt的证书链现在主要依赖ISRG Root X1这个根证书。如果你的Java程序跑在比较老的JDK上比如JDK 8u141之前的版本默认信任库cacerts里没有内置ISRG Root X1那么这些Java程序访问Let‘s Encrypt签发的HTTPS站点时会直接报PKIX path building failed因为它不认识这个根证书。解决方案通常是两个方向要么升级JDK版本到一个内置了新根证书的版本要么把这个根证书手动导入JDK的cacerts信任库。如果是一些没法升级的老系统还要注意证书链里是否包含了跨签的中间证书某些老客户端需要靠中间证书里的交叉签名来补齐信任路径。这也是为什么我建议在排查浏览器能打开但Java程序报证书错误这类问题时先去确认客户端环境的信任库而不是一上来就怀疑服务器证书配置有问题。6.2 有条件续期机制背后的工程理念Lets Encrypt把证书有效期定90天、续期阈值定30天这个设计在运维上是有深意的。短有效期意味着即使私钥泄露事故影响面被限定在90天内也意味着你必须把事情自动化不能靠人肉记日期。续期阈值30天则是给自动化留了足够的重试窗口——一次定时任务失败还有将近一个月的时间去修复不至于第二天就挂掉。理解了这套设计逻辑你对自动续期的态度就会有转变不要神化它也不要轻视它。它就是一套定时器 条件判断 自动申请的脚本组合。它的可靠性来源于你服务器环境的稳定性一旦环境变了脚本不会像人一样主动适应只会静静地失败、跳过、等待。6.3 迁移后还有一类证书问题和Lets Encrypt完全无关排查过程中我经常被问到一个看起来很像的问题为什么Charles、Fiddler这类抓包工具突然报证书过期这类工具使用的证书是自己动态生成的根证书用于解密HTTPS流量。抓包工具的证书过期其实是指你手机或电脑上安装的那个抓包根证书过期了跟网站服务器上的Lets Encrypt证书没有半点关系。处理方式也完全不同你需要去抓包工具的设置里生成一个新的根证书然后重新安装到设备上旧的删掉。把这两类证书问题混为一谈是新手最容易犯的错。前者是服务端证书后者是客户端代理证书两者无论是证书格式、信任链建立方式还是更新路径都不一样。排查线上证书问题前先想清楚这个证书是装在服务器上的还是装在用户设备上的方向对了效率就上来了。6.4 迁移这件事最值钱的经验是Checklist回头再看这次迁移排查我最大的体会是迁移本身不难难的是迁移后的环境一致性。你在一台机器上精心配置的自动续期机制换一台机器如果不显式恢复它就跟没存在过一样。这也是为什么我现在做迁移第一件事永远是列Checklist把certbot、logrotate、系统定时器、防火墙规则这些看不见的运维基础设施全部列进去和数据库、网站文件一样作为一等公民对待。以后再遇到服务器迁移关于证书部分我会直接这样操作备份整个/etc/letsencrypt和相关systemd配置迁移完成后恢复目录、检查路径、systemctl enable --now certbot.timer、跑一次certbot renew --dry-run全部绿灯后才宣布迁移完成。整个过程十分钟但这十分钟能省掉未来半个月的线上排查时间。你可能会说这些环节看起来都太基础了但恰恰是这些基础环节在迁移的高压环境下最容易被忽略。
返回列表