ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

服务器迁移后Let‘s Encrypt自动续期失效,排查与恢复指南

服务器迁移后Let‘s Encrypt自动续期失效,排查与恢复指南 1. 事故现场迁移后证书一夜之间失效昨天凌晨两点半监控告警把我从床上拽了起来。一家老客户的应用服务器前两天刚做完迁移从旧物理机搬到新云主机域名解析也改了A记录。本想着周末迁移、周一检查结果还没到周一用户群里就开始有人反馈“网站打不开了”“浏览器提示证书过期”。我打开浏览器亲自访问Chrome直接甩了一个经典的红色警告页NET::ERR_CERT_DATE_INVALID。再看一眼证书信息好家伙到期时间赫然是昨天——就在我迁移完成、切换DNS之后没多久。这个现象特别有迷惑性。因为“Lets Encrypt 证书”这个关键词在我们这行几乎等同于“免费、自动续期、不用管”。很多人的第一反应都是迁移服务器和证书续期有什么关系证书不是会自动续吗怎么换个服务器反而过期了我当时也是这么想的所以刚开始根本没往证书续期机制上想而是先怀疑是不是新服务器时间不对、是不是NTP没同步甚至怀疑是不是中间网络设备劫持了HTTPS流量。排查了一圈时间正常、链路正常最后回到服务器上用openssl一看证书文件才确认问题出在“新机器上压根没有能触发自动续期的完整配置”。先说结论Lets Encrypt 证书本身确实是自动续期的但它自动续期的前提是续期工具比如certbot得在你现在的服务器上装好、配置好、并且定时任务还在跑。一旦换了服务器旧的定时任务、旧的工作目录、旧证书私钥、旧的续期配置文件全部留在旧机器上新机器相当于从零开始它根本不知道有张证书需要续期自然不会“自动”给你续上。这篇文章我就从这次迁移事故切入把 Lets Encrypt 自动续期的机制、服务器迁移为什么会导致续期失效、以及遇到“证书到期”该怎么排查恢复一次讲清楚。适合正在做服务器迁移、想搞懂certbot续期原理、以及被证书问题折腾过但没时间深究的人。2. 续期机制拆解自动续期到底是怎么工作的2.1 定时任务与续期窗口Lets Encrypt 官方推荐的客户端是 certbot它自动续期的核心就是一个系统定时任务。以常见安装方式为例用apt安装的版本会注册一个certbot.timer的 systemd 定时器用snap安装的版本对应的是snap.certbot.renew.timer用dockerized方式跑的则是你自己在宿主机上配置一条cron每天执行docker run ... renew。这个定时任务默认每天跑两次但注意它不是每次跑都真的去续期。certbot 内部有一个续期窗口逻辑只有当证书剩余有效期少于30天时才会真正向 Lets Encrypt 发起续期请求。距离到期时间还早的话命令直接退出什么都不做。这个设计很合理——Lets Encrypt 签发的证书有效期是90天如果每天到期前30天才动手那一个证书在生命周期内大约只会被续期2~3次不至于每个小时都去打一遍CA接口也能有效降低CA侧负载。但这里就埋了一个坑很多人在旧服务器上装好证书之后从没手动跑过续期看到 “certbot renew” 定时任务存在就觉得高枕无忧。实际上如果你平时不关注证书还剩多少天等到迁移时把环境一换定时任务断了证书就可能在几周后静默到期然后用户蜂拥而至地来骂你。2.2 成功续期依赖的几个前置条件certbot 在续期时要真正确认“你仍然拥有这个域名”所以它必须完成一次域名验证。这意味着续期是否成功取决于一组条件续期配置存在certbot 的续期靠/etc/letsencrypt/renewal/域名.conf这个文件来记录验证方式、证书路径、加解密选项等信息。配置文件没了certbot 就不知道该去续哪个证书更不知道该用什么方式验证。私钥可访问证书私钥在/etc/letsencrypt/live/域名/下普通用户无权读取必须用root或sudo。迁移后如果权限不对certbot 无法读取私钥也无法完成续期。验证端口可访问默认的 HTTP-01 验证方式要求域名能通过80端口访问到这台服务器且服务器上要有Web服务响应 Lets Encrypt 验证服务器发来的请求。迁移后如果80端口没开、防火墙没放行、或者域名解析还没指向新机器验证就会失败。DNS已生效如果用的是 DNS-01 验证比如通配符证书则需要你的 DNS 服务商API凭证可用并且域名解析已经切到新环境。DNS的TTL、缓存策略都会影响验证成功率。系统时间正常证书签发和续期都是跟时间强相关的系统时间偏差过大会导致证书验证直接失败。一句话总结自动续期不是魔法是一个依赖“环境连续性”的定时任务。环境里任何一环断了续期就会悄悄失败。2.3 用生活化类比理解迁移为何切断续期我经常跟团队里的新人说把证书续期想象成“银行卡自动缴费”你签约的时候把银行卡号填好了每个月银行自动从卡里扣钱所以你感觉“不用管”。但如果你换了新银行卡却没有去缴费平台重新绑定新卡那下个月的自动扣款就失败了。服务器迁移就是这样旧机器上的“自动缴费协议”renewal配置文件、银行卡私钥、缴费通道80端口/DNS验证全都留在了旧环境。新机器上可能只有刚刚切过来的域名解析其他的一概没有。你指望它“自动续期”但它连自己欠费了都不知道。3. 迁移场景下的两条恢复路径迁移证书还是重新签发搞清楚原理之后恢复就分两种情况了。我在实际运维中把这两种情况都遇到过处理方式差别很大。3.1 路径A旧服务器还能登录直接打包迁移证书如果迁移时间窗口比较从容或者旧服务器还没销毁最好的办法就是连同证书和certbot配置一起迁过去而不是在新服务器上重新签发。这样能保证私钥不变、证书续期记录连续减少后续麻烦。具体操作分四步第一步在旧服务器上打包证书目录sudo tar czf letsencrypt-backup.tar.gz /etc/letsencrypt这个目录包含了 live、archive、renewal 三个关键子目录分别对应当前证书软链、历史证书文件、续期配置。务必用root打包否则会漏掉权限或文件。第二步把包传到新服务器并解压sudo tar xzf letsencrypt-backup.tar.gz -C /注意解压时目录结构必须完整解压后检查sudo ls -l /etc/letsencrypt/live/example.com/应该能看到 cert.pem、chain.pem、fullchain.pem、privkey.pem 四个文件并且它们是软链指向 ../../archive/example.com/ 下的具体版本文件。第三步在新服务器上安装certbot不同系统安装方式不一样我用Ubuntu比较多一般是sudo apt update sudo apt install certbot如果你原来用的是snap版就安装snap版。这里有个经验最好把certbot的版本和旧服务器保持一致或者至少确认支持你原来的验证插件。如果你原来用certbot-dns-cloudflare这类DNS插件做通配符证书迁移后忘了装对应插件那么后续续期会直接报“没有这个验证器”。第四步测试续期是否真正可用sudo certbot renew --dry-run如果输出里出现类似 “Congratulations, all simulated renewals succeeded” 的字样说明配置和验证链路都是通的。这个时候哪怕证书还没到期你也知道“自动续期”这个机制在新环境里是活的。注意用--dry-run测试时certbot 会往 Lets Encrypt 的测试环境staging发送请求不会真的替换你正在用的证书放心跑。迁移完之后别忘了重启或reload一次Web服务让进程加载新路径下的证书。我见过不止一次迁移时证书文件复制得没问题但Nginx还缓存着旧路径的证书句柄结果一直往外吐旧证书前端却显示新证书排查半天才发现忘了 reload。3.2 路径B旧服务器已经没了只能重新签发如果旧机器已经销毁、备份也没留那也别慌。Lets Encrypt 签发免费证书很快重新签一张的成本很低只是要注意几个点。首先确认域名解析是否已经切到新服务器并且新服务器的80端口HTTP-01或DNS控制权可用DNS-01。最简单的签发方式sudo certbot --nginx -d example.com -d www.example.com如果你用的是Apache就换成--apache如果你不想让certbot自动改Web服务器配置可以用 webroot 方式sudo certbot certonly --webroot -w /var/www/html -d example.com对于多域名或通配符证书则必须用DNS验证。比如 Cloudflare 托管DNS的场景sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.cloudflare这里要特别提醒重新签发后私钥完全变了。如果证书要部署到CDN、负载均衡、或者对外API网关一定要把所有引用旧证书的地方都更新掉。尤其是老系统里可能硬编码了证书指纹的情况重新签发后指纹变了对端校验直接失败。3.3 我的建议迁移把证书当“状态数据”对待无论走哪条路径我个人的经验是证书迁移本质上是在迁移“状态”不是迁移“文件”。应该连同续期配置、验证凭据、定时任务一起考量。一个完整的迁移检查清单大致长这样是否备份并迁移了/etc/letsencrypt整个目录新服务器是否安装了certbot及对应的验证插件如果是DNS验证DNS服务商的API密钥是否迁移了是否放在新服务器上正确的位置定时任务是否启动systemd timer是否enable防火墙/安全组是否开放了80或443端口域名解析哪条记录指向新服务器TTL设置是否合理迁移后是否跑过certbot renew --dry-run验证续期链路我在多个项目里把这张清单写进了运维手册效果比临时抱佛脚好太多。4. 排查工具箱证书问题的定位手段4.1 快速判断证书是否过期遇到证书问题第一件事永远是确认到底过期没有、有没有被吊销、中间链是否完整。命令行三板斧非常实用。查看本地证书文件的有效期sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem查看线上服务实际下发的证书有效期echo | openssl s_client -connect example.com:443 -servername example.com 2/dev/null | openssl x509 -noout -dates查看证书到底是谁签发的echo | openssl s_client -connect example.com:443 -servername example.com 2/dev/null | openssl x509 -noout -issuer -subject如果-issuer结果不是 Lets Encrypt 的中间证书而是某个自签名证书那说明你的Nginx/Apache配置指向了错误的证书文件这种情况很常见尤其是在你搞过本地自签名测试之后忘改回来。4.2 确认定时器有没有在跑迁完服务器最怕的就是“以为在跑其实没跑”。用systemd的话检查定时器很简单sudo systemctl status certbot.timer sudo systemctl list-timers | grep certbot如果是snap版sudo systemctl status snap.certbot.renew.timer定时器存在不等于下次续期会成功。certbot 的续期日志一般写在/var/log/letsencrypt/letsencrypt.log有问题直接翻日志能定位到90%的原因。日志路径迁移后可能因为目录不存在导致certbot压根没法写日志这种情况我曾经遇到过原因是新系统上/var/log/letsencrypt目录没建导致续期任务启动即失败。把定时器置为开机自启并立刻触发一次测试sudo systemctl enable certbot.timer sudo certbot renew --dry-run4.3 常见故障类型速查表现象可能原因排查/解决浏览器报证书过期证书确实过期续期未成功或未执行查看证书到期时间跑dry-run看失败原因浏览器报证书不受信任中间链不完整只配了cert.pem没配fullchain.pem检查Nginx ssl_certificate是否指向fullchain.pem报 NET::ERR_CERT_AUTHORITY_INVALID老系统不信任 Lets Encrypt 根证书ISRG Root X1更新系统的CA证书库或者安装旧根证书交叉签名手机App访问提示证书无效App内置证书列表太旧或中间链缺失确认下发的是完整证书链必要时做证书链修复certbot renew 提示DNS验证失败DNS解析还没切到新服务器或API凭证失效检查A记录、DNS服务商API key定时任务存在但证书一直不续续期窗口还没到剩余30天属于正常现象用 --dry-run 验证能续即可服务报权限错误/etc/letsencrypt 权限不对用sudo chmod或chown修正4.4 老系统与根证书兼容性这里单独提一下近期在社区里讨论很多的兼容性问题。Lets Encrypt 的证书链现在主要走 ISRG Root X1而某些老旧的运行环境——比如旧版本的JDK、某些嵌入式设备、以及多年未更新的系统CA证书库——预设信任列表里没有 ISRG Root X1导致即使证书本身没问题客户端依然会报“不信任”。Java相关的场景尤其典型。我排查过一个老客户系统链路里所有浏览器都正常唯独他们内部Java服务访问HTTPS接口时狂报PKIX path building failed最后定位到是JDK版本太老内置的cacerts里根本没有ISRG Root X1。解决方案有两个升级JDK版本跟上新的根证书把 ISRG Root X1 手动导入到 JDK 的 cacerts 里。这类问题跟证书“是否过期”无关但在迁移后特别容易冒出来因为迁移往往顺带升级了操作系统或运行时新旧信任链不一致的差异一下就暴露了。4.5 证书到期监控脚本经过这次事故我把证书监控纳入了常规巡检。一个最简单的方案是写个Shell脚本每天检查所有证书剩余天数低于20天就推送告警到钉钉或企业微信机器人。#!/bin/bash for cert in /etc/letsencrypt/live/*/fullchain.pem; do domain$(basename $(dirname $cert)) enddate$(openssl x509 -enddate -noout -in $cert | cut -d -f2) end_ts$(date -d $enddate %s) now_ts$(date %s) remain$(( (end_ts - now_ts) / 86400 )) if [ $remain -lt 20 ]; then curl -s -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyYOUR_KEY \ -H Content-Type: application/json \ -d {\msgtype\:\text\,\text\:{\content\:\证书告警: $domain 还剩 $remain 天\}} fi done把脚本放进/etc/cron.daily/或者接到系统的监控平台里Zabbix、Prometheus黑盒监控都可以实现证书过期检测就可以做到“到期前先知”。5. 避坑指南以后再也不让证书坑你5.1 非标准安装方式的坑很多人在生产环境里用pip install certbot或者某种非常规方式安装certbot一旦系统升级、Python版本变动certbot就可能“原地消失”定时任务悄悄失效。迁移服务器时更明显新机器上根本没装这个组件你却以为它在跑。我的建议是生产环境优先使用官方仓库或snap方式安装不要图省事用pip全局装。snap会自动管理更新定时器也更规范减少“莫名其妙不执行”的概率。5.2 证书文件千万别放临时目录有同行为了图省事把证书文件放到/tmp或项目目录下结果服务器一重启、目录一清理证书全没了。Lets Encrypt 的默认路径/etc/letsencrypt是经过验证的、权限控制合理的目录老老实实用默认路径别自己发挥。如果你确实需要把证书拷贝到其他目录比如给Nginx容器挂载建议写一个小的同步脚本并在证书续期成功后自动执行拷贝。否则超大可能性出现“certbot续期成功了Nginx还在用旧证书”的诡异问题。5.3 续期后必须reload服务续期成功不会自动重启Nginx、Apache、Postfix等依赖证书的进程。这让很多人踩过坑证书明明显示新签发但线上服务对外吐的还是旧证书。根因就是没有reload。所以在certbot的续期成功钩子renewal hook里应该加上sudo systemctl reload nginx具体做法是编辑/etc/letsencrypt/renewal-hooks/deploy/下的脚本或者直接写在/etc/letsencrent.ini里的deploy-hook systemctl reload nginx。5.4 多节点部署的同步问题如果证书不只部署在一台机器上而是同时用于负载均衡后的多台后端、或者CDN源站那证书续期后的同步就是一个系统工程。Lets Encrypt 免费证书的生命周期短三个月一换意味着你至少每年要处理四次“多节点同步”。比较省心的方案有两类使用ACME客户端自带的部署钩子例如certbot签完证书后自动通过scp或rclone同步到其他节点把证书集中到一台“证书管理机”上由它统一签发和同步其他节点只负责拉取和reload。我个人更倾向第二种集中管理、集中监控避免每个节点都有一份独立续期配置最终导致部分节点悄悄失效。5.5 通配符证书的DNS验证特殊要求如果你签了*.example.com这种通配符证书验证方式必须使用DNS-01也就是说certbot需要能修改你的DNS解析记录。这个能力通过DNS服务商的API实现每个服务商的插件不同。这里有两个极易踩的坑DNS服务商的API密钥有有效期迁移服务器后密钥可能已过期续期会失败如果域名托管在一个服务商而CDN或DNS解析在另一个平台certbot可能改了A平台的记录但实际生效的解析却走的是B平台会造成验证死循环。所以通配符证书迁移后第一件事就是测试certbot renew --dry-run确认DNS API通道可用别等到证书真过期了才想起来。5.6 迁移时机的选择最后说一个看似与技术无关但极其重要的点迁移时机。如果你的证书剩余天数已经少于30天而且新老环境之间存在较长的切换窗口比如域名解析的TTL设置很久、或需要客户本地刷新DNS缓存那你最好在迁移前先把证书续期做一次。否则切换后没几天就要面对“证书到期”和“环境切换”两个问题同时爆发的双重压力。我通常的做法是如果证书剩余不足45天就先在旧服务器上强制续期通过certbot renew --force-renewal确认续期成功后再开始迁移。这样整个迁移窗口内证书都是新签的有效期有90天容错空间大很多。6. 写在最后的经验之谈折腾完这次迁移事故我的体会是自动续期是个好东西但它只对“环境不改变”这一前提负责。服务器迁移本身就是对环境的彻底改变如果你不手动把续期机制“搬过去”它自然就会失效。证书这件事本质上是在提醒我们运维里的“自动化”不是一劳永逸而是需要定期验证的承诺。最后再分享一个小技巧迁移完成后我习惯在新服务器上手动执行一次certbot renew --dry-run然后把输出截图存到迁移记录里。这样不只是为了让领导放心更是在给自己留一个“续期链路可用”的时间锚点。等下一次证书到期前我再对比这个基线就很容易判断是哪一步出了问题。希望这篇复盘能帮你少走一些弯路。真遇到证书问题别慌按这个思路逐层排查大概率花不了半小时就能恢复。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进