恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
服务器迁移后Let‘s Encrypt证书过期?自动续期机制排查与修复指南
首页
资讯中心
/
服务器迁移后Let‘s Encrypt证书过期?自动续期机制排查与修复指南
服务器迁移后Let‘s Encrypt证书过期?自动续期机制排查与修复指南
发布时间:2026/9/16 3:52:06
你有没有遇到过这种情况服务跑得好好的突然某天早上收到一堆“网站打不开”“SSL证书错误”的告警。打开浏览器一看整页的红色警告——证书过期。更气人的是当初明明配置了 Lets Encrypt 证书自动续期理论上它会自己更新为什么到头来还是过期了我最近就踩过一次类似的坑起因是一次服务器迁移。旧机器用了好几年性能吃紧安全补丁也跟不上了于是把所有服务迁到新机器。迁移时我自认为很细心代码、配置、数据库、静态文件全都拷过去了nginx 也重新配置好了域名解析切到新 IP 之后网站访问一切正常。直到两个月后证书过期HTTPS 彻底打不开。这件事让我重新把 Lets Encrypt 的续期机制从头到尾捋了一遍。这篇文章就从这个真实事故说起讲清楚 Lets Encrypt 证书到底会不会自动续期、自动续期依赖哪些条件、服务器迁移为什么会把续期弄坏以及完整的排查和修复流程。适合运维、个人站长、还有所有自己折腾服务器的开发者参考。1. 一次迁移事故网站突然打不开证书过期了1.1 事故现场事情发生在一个周六下午。当时我正准备给博客发一篇新文章后台却怎么也登不进去。一开始以为是数据库挂了SSH 上去看MySQL、nginx、PHP 进程全都在线CPU 和内存也正常。用 curl 访问本地站点返回 200但只要从外网用域名访问浏览器就报证书无效。点开证书详情一看过期时间是当天的凌晨 2 点左右。我的第一反应是不对啊迁移新服务器的时候我明明装过 certbot续期定时任务应该也在怎么会过期然后我登录服务器看了一眼 certbot 的状态才发现问题比我想象的复杂。旧服务器上的证书文件确实被我用 scp 拷到了新机器nginx 也能正常加载这些文件所以网站一开始跑得顺顺当当。但这套“能跑”是个假象——迁过来的只是证书文件本身续期需要的整套环境并没有跟着过来或者说过来了但不完整。这类事故非常典型。很多人迁移后只验证了“网站能访问”没验证“证书能续期”。证书是静态文件拷贝过去就能用自动续期是动态机制牵扯定时任务、配置、网络验证通道任何一环断了结果就是 90 天后准点爆炸。1.2 免费的背后是短周期要理解这次事故先得接受一个事实Lets Encrypt 的证书有效期只有 90 天。这不是随随便便定的而是刻意设计成短周期。短周期有几个实际好处。私钥泄露风险被压缩到 90 天内就算某张证书泄漏影响窗口也有限证书撤销机制在短周期内变得没那么重要反正很快会过期同时短周期倒逼所有使用者把部署流程自动化谁还手动换证书谁就等着每个月被自己的证书折腾死。很多人第一次接触 Lets Encrypt 时会对“免费 90 天”有本能的不信任觉得免费的东西肯定不稳定。实际上这套机制是经过大量生产环境验证的全球有上亿个站点在使用。它的核心根本不是那几十 K 的证书文件而是围绕“自动续期”建立的一整套可重复的签发验证流程。只要这套流程没被破坏证书就会像空气一样始终存在你根本感觉不到它的存在。1.3 “自动续期”的真相这里必须说一句可能会得罪人的话Lets Encrypt 证书本身并不会“自动续期”。它只是一个静态文件没有自我更新的能力。真正的续期动作是由你服务器上的 certbot 或类似 ACME 客户端完成的。换句话说Lets Encrypt 提供的是一套可以随时重新签发的 API而你的服务器上必须有一个程序定期去问它“我这几个域名的证书该换了吗如果快过期了就重新给我签一张”。如果这个程序没跑或者跑了但验证失败证书就是一张废纸到期照样过期。所以“自动续期”严格来说是三个条件的叠加证书管理程序比如 certbot已经安装并且配置正确定时任务systemd timer 或 crontab能定期唤醒它续期时的域名验证通道是通的也就是 ACME 协议规定的验证请求能从公网到达你的服务器。这三个条件任何一个断裂“自动续期”都会变成“自动假装在续期”。服务器迁移的时候最容易裂的就是第三条其次是第一条里的配置路径。2. 自动续期是怎么运作的迁移又是怎么把它弄坏的2.1 续期不是魔法是一套定时任务以最常见的 certbot 为例。安装 certbot 后它会做一个看起来不起眼但至关重要的事注册一个定时任务。使用 apt 安装的版本通常会在/etc/cron.d/certbot写一条定时任务使用 snap 安装的版本则会注册一个 systemd timer叫certbot.timer。这个定时任务默认每天执行两次大概频率是每 12 小时一次。任务执行的命令本质是certbot renew。这条命令会遍历/etc/letsencrypt/renewal/目录下的所有证书配置逐个检查当前证书剩余的有效天数。注意一个关键点certbot renew不是每次都会真的发起续期。只有当证书剩余有效期小于 30 天时它才会连上 Lets Encrypt 服务器重新签发。如果剩余时间还长命令会直接跳过连网络请求都不发。所以你会看到有人手动执行certbot renew输出却是“Cert not yet due for renewal”就去怀疑是不是 certbot 坏了——其实这反而是正常状态。这个设计很聪明。每天都检查一次但只在最后 30 天才动手既保证有足够余量去处理失败和重试又不会频繁骚扰 Lets Encrypt 服务器。换句话说证书在过期前 30 天到过期当天之间随时都可能触发续期具体哪天取决于你上次成功续期的时间和定时任务跑的时间。2.2 续期验证的两条通道证书要重新签发Lets Encrypt 服务器必须先确认你对这个域名有控制权。ACME 协议里最常用的是两种验证方式。第一种是 HTTP-01 验证。Lets Encrypt 服务器通过 80 端口访问一个特定 URL比如http://你的域名/.well-known/acme-challenge/一串随机字符。这串随机字符由 certbot 生成并临时写到网站根目录里CA 服务器能访问到就说明域名背后的服务器在你的控制下。验证通过后证书签发随机文件删除。第二种是 DNS-01 验证。certbot 要求你在域名的 DNS 解析记录里添加一条 TXT 记录内容也是一串随机值。CA 服务器去查询这条 TXT 记录能查到就说明你有 DNS 管理权限。这种验证方式不需要 80 端口但配置起来麻烦一些通常用于不能对外开放 80 端口的场景或者泛域名证书。绝大多数单人、单机、单域名的场景用的都是 HTTP-01因为它最简单无需手动操作 DNS。但也正是因为它依赖 80 端口和网站的根目录迁移后最容易出问题。2.3 迁移后哪些环节最容易“断链”迁移服务器时大多数人会拷贝什么网站代码、数据库、nginx 配置、证书文件。运行certbot renew --dry-run时它会读取旧服务器上的续期配置里面记录着当初签发的认证方式和路径。我曾经以为只要证书文件拷过来就万事大吉。但迁移后真正决定“能不能自动续期”的是下面这些容易被忽略的点续期配置文件里的 webroot 路径很可能和旧服务器不一样。旧服务器上网站根目录是/var/www/html新服务器上你把它放到了/opt/www/blogcertbot 还是去老路径写验证文件结果文件写了但 nginx 根本不会去那里找。定时任务没有跟着启用。apt 安装的 certbot 会写 crontab但如果你用 snap 安装、或者从旧机器手动拷贝systemd timer 可能没启用或者启用了但被 mask 了。防火墙或云安全组没有放行 80 端口。很多云服务器默认只放行 443证书验证用的 80 端口被挡在外面ACME 请求根本进不来。DNS 解析没有完全生效。迁移后你把域名切到新 IP但可能存在缓存、子域解析不全、CDN 回源配置过时等情况导致验证请求打到旧服务器或错误节点。权限问题。拷贝/etc/letsencrypt后所有者和权限可能变化certbot 的 snap 版对目录权限有严格要求权限不对会导致续期任务直接报错。我那次事故四个原因撞到了一起webroot 路径不对、80 端口被防火墙挡了、旧 crontab 没有迁过来、证书文件权限变成了 root root 但 snap 版 certbot 要求特定 owner。一个下午从排查到修完真是把能踩的坑全踩了一遍。3. 一步步排查从状态确认到手动续期3.1 先确认证书到底还活着没有遇到证书问题第一步永远是确认现状别靠猜。最直接的方式是在服务器上查看证书文件的实际过期时间。openssl x509 -enddate -noout -in /etc/letsencrypt/live/你的域名/fullchain.pem这条命令会输出类似notAfterMay 10 02:00:00 2025 GMT这样的结果一眼就能看出证书的真实过期时间。但注意这个命令读的是证书文件本身如果 nginx 当前加载的证书和你查看的路径不是同一个结果就不一致。所以更保险的做法是看 nginx 实际加载的是哪个文件。nginx -T | grep ssl_certificate然后把 nginx 配置里的路径拿去做 openssl 查询。这一步能确认你看到的证书确实是浏览器访问时拿到的那张。另外还可以用certbot certificates查看 certbot 视角下的证书状态。这个命令会列出所有被 certbot 管理的证书、域名、过期时间、续期方式和续期配置路径。如果你迁移时拷贝过证书目录但 certbot 本身是新装的certbot certificates可能什么都列不出来这说明 certbot 并不知道这些证书的存在自然也就谈不上自动续期。3.2 续期任务还在不在确认完证书状态接下来要确认续期任务到底有没有在运行。apt 安装 certbot 的 Debian/Ubuntu 系统先看 crontabcat /etc/cron.d/certbot正常内容会有类似0 */12 * * * root test -x /usr/bin/certbot perl -e sleep int(rand(3600)) certbot -q renew的一行。注意它每 12 小时跑一次并且有一个随机延迟避免所有机器同时打爆 Lets Encrypt 服务器。snap 安装的版本则要看 systemd timersystemctl list-timers | grep certbot如果有输出说明 timer 存在。再执行systemctl status certbot.timer看是否 active。如果 timer 被 enable 了状态应该是 active (waiting)并且 Next trigger 是下一次执行时间。如果状态是 inactive 或 dead说明定时任务根本没启动。还有一种情况是 certbot 是旧机器上的二进制直接拷贝过来的没有经过正式的安装流程那定时任务大概率根本不在了。这种情况下即使证书文件都在也永远不会有自动续期的动作发生。3.3 用 dry-run 把问题暴露出来排查到这一步就可以动用最有价值的一条命令了certbot renew --dry-run--dry-run会模拟完整的续期流程读取配置、连接 Lets Encrypt 测试环境、执行域名验证、尝试签发但不会替换你现有的证书文件。因为验证流程是真实发生的所以它能百分之百暴露“如果真的到了续期那天会不会成功”这个核心问题。执行一次 dry-run 之后你会得到三种可能的输出。第一种是Congratulations, all simulated renewals succeeded这说明续期链路是通的问题大概率只在定时任务没启用。第二种是某一个或某几个域名验证失败报错里通常会写明是 timeout、404 还是权限问题。第三种是配置根本读不到直接报证书找不到、配置文件损坏之类的错误。跑完 dry-run 后顺手看一眼 certbot 自己的日志tail -n 50 /var/log/letsencrypt/letsencrypt.log日志里会记录每次 renew 的详细过程包括失败原因。很多时候日志给的信息比任何排查命令都直接。我那次就是靠日志定位到/var/www/html下找不到写入的验证文件才意识到 webroot 路径配置已经和实际网站目录对不上了。3.4 常见验证失败原因速查dry-run 报错的常见原因我整理成了一张表排查时可以对照着看。报错特征常见原因快速检查方法Failed to connect to xx port 80: Connection refused服务器防火墙或云安全组没放行 80 端口本机执行 ss -lntpTimeout during domain verification80 端口不通或 DNS 解析没到新服务器用curl -v http://域名/.well-known/acme-challenge/测试验证公网可达性404 Not Foundwebroot 路径配置错误或 nginx location 没有正确映射到验证目录检查续期配置文件里的webroot_path是否和实际网站根目录一致Invalid response验证目录被 CDN 缓存污染或反向代理规则拦截检查 nginx 对/.well-known/的 location 配置权限不足 Permission denied/etc/letsencrypt或网站根目录属主不对检查目录 owner 和权限snap 版 certbot 对严格权限敏感排查时记住一个原则验证流量的完整路径是从公网到服务器 80 端口再到网站目录下的验证文件。这条链路中间任何一个环节不通验证就会失败。千万别只盯着最后一步的 certbot 命令报错前面几步也要按顺序走一遍。4. 实操修复重建证书与续期链路4.1 决策保留原证书还是重新签发排查结束后接下来是修复。但动手前先想清楚一个问题你要保留从旧服务器拷过来的证书还是干脆重新签发一张全新证书我建议绝大多数情况直接重新签发。原因有几点重新签发的流程干净续期配置是在新服务器上从零生成的不会残留旧路径、旧权限的历史包袱旧证书如果已经快过期续期也要走一遍完整验证和重新签发的工作量没有任何区别而且迁移场景下旧服务器已经不用了旧证书保不保留根本没有意义。什么情况下才值得保留比如域名指向的是重要线上服务担心重新签发过程中短暂不可用。但实际上 Lets Encrypt 的签发和替换可以无缝完成certbot 签发新证书时会替换/etc/letsencrypt/live/下的软链接nginx reload 也只是秒级重启。所以把心放回肚子里重新签发是更稳妥的选择。4.2 重新签发的完整命令在动手前先把 DNS 解析确认好。执行dig short 你的域名或者nslookup 你的域名确保解析结果指向新服务器的 IP。如果 DNS 还在缓存连签发验证都过不了。这一步非常关键我在实际操作中见过太多人跳过这一步结果 renew 的时候验证请求被送去了旧服务器。确认完 DNS如果 nginx 已经部署并且 80 端口能访问最简单的方式是用 certbot 的 nginx 插件certbot --nginx -d example.com -d www.example.com这个模式会自动检测 nginx 配置写入证书后顺手帮你修改 SSL 配置一条命令解决签发和部署两件事。但它对 nginx 配置有一定要求如果 nginx 配置里有语法错误或非标准目录结构可能失败。如果不想让 certbot 动 nginx 配置更可控的方式是 webroot 模式certbot certonly --webroot -w /var/www/blog -d example.com -d www.example.com这里-w指定的是网站根目录certbot 会把 ACME 验证文件写到这个目录的.well-known/acme-challenge/下。要保证 nginx 对/.well-known/的请求会落到这个目录通常默认静态文件配置已经支持不需要额外操作。webroot 模式签发完后需要手动把证书路径配置到 nginx 里。证书文件在新服务器的/etc/letsencrypt/live/你的域名/fullchain.pem和privkey.pem。4.3 重建自动续期任务证书签发成功只是完成了第一步接下来要把自动续期的“发动机”重新装回去。如果 certbot 是通过官方 apt 源安装的安装过程会自动创建/etc/cron.d/certbot理论上不用手动干预。但你刚从一次迁移事故里爬出来千万别只靠“理论上”。老老实实检查一遍ls -l /etc/cron.d/certbot cat /etc/cron.d/certbot如果文件不存在或内容不对可以手动创建。要注意的是不同发行版的默认存储路径可能不一样。CentOS/RHEL 用 yum 安装时certbot 通常注册为 systemd timer操作方式如下systemctl enable --now certbot-renew.timer systemctl list-timers | grep certbotUbuntu 上如果 certbot 来自 apt也可能会生成certbot.timer所以我的建议是 crontab 和 systemd timer 两者都查一遍确认至少有一个在运行。绝不要两个都出现那倒无所谓更怕的是两个都没有。同时也把 nginx 配置里的证书路径确认一下。我在迁移时犯过一个低级错误nginx 配置写的是旧服务器上的绝对路径/etc/letsencrypt/live/example.com/fullchain.pem但新服务器上这个目录确实存在因为安装的时候用默认方式又签发过一次。前两个月没问题直到 certbot 尝试更新时在该路径下写入了新版本nginx reload 后把新证书加载了出来才发现配置里的路径是旧目录的软链接。4.4 验证续期链路真的通了修复完成后最要紧的一步是验证。你不能真的等 60 天看它会不会自动续期那等于拿生产环境当实验品。正确做法是用 dry-run 再走一遍流程certbot renew --dry-run看到Congratulations, all simulated renewals succeeded之后再确认定时任务的执行状态systemctl list-timers | grep certbot systemctl status certbot.timer journalctl -u certbot.service --since 1 hour agojournalctl 那一步能确认 timer 触发后 certbot 真实跑了一次并且结果是成功还是跳过。如果你只想确认任务确实会跑可以手动触发一次服务systemctl start certbot.service这一步会真实执行一次certbot renew不是 dry-run。如果证书剩余时间大于 30 天它会显示跳过。如果之前手动整理过刚好有证书在可续期窗口内它就会直接续。看到日志里出现 “no renewals were attempted” 或者 “The following certificates were successfully renewed”你就能确认整条链路完全恢复正常了。5. 迁移踩坑实录与避坑心得5.1 迁移场景问题速查表这次迁移事故之后我把所有可能踩的坑整理成了下面这张速查表后续每次迁移服务器、或者帮朋友排查证书问题时直接按表自查。问题可能原因处理建议网站能访问但证书很快就过期证书文件是旧机器拷来的续期任务没迁直接重新签发而不是修旧配置定时任务存在但从不触发systemd timer 被 disable 或 cron 服务没启动systemctl enable --now certbot.timerdry-run 报 timeout防火墙挡了 80 端口或 DNS 未指向新 IP先dig确认域名解析再检查安全组证书续期了但 nginx 还加载旧证书证书路径配置错误或忘了 reloadnginx -t检查配置然后systemctl reload nginx多域名证书其中一个验证失败其中一个域名的 DNS 或 webroot 有问题多域名证书是“木桶效应”一个不通过整个都不续续期日志报权限错误/etc/letsencrypt目录 owner 不对手动chown到 certbot 运行用户5.2 几条用时间换来的经验如果迁移时能重新来一遍我一定会执行下面这几条经验每一条都是用真金白银的时间换来的。第一条迁移后的第一件事不是测网站而是测证书续期链路。网站能访问只是配置文件层面的成功续期链路才决定这个网站下个月还在不在。所以迁移完先跑一次certbot renew --dry-run再处理其他细枝末节。我就是因为偷懒把这一步跳过了才换来一个难熬的下午。第二条把/etc/letsencrypt当作一个有生命的状态目录来对待。你拷贝一个数据库备份到新机器不会直接投入使用至少得跑个备份还原、查一下数据完整性。证书目录也一样它不是无脑拷过去就行。而且现在很多发行版对 certbot 的目录结构有严格预期拷贝环境不完整certbot 宁可报错也不会自作主张去“修复”这就是安全软件的本分。第三条建议你在服务器外配一个证书剩余天数的监控。比如 Cloudflare 或者免费的 UptimeRobot、SslChecker 之类的服务都是免费的这些服务会定期探测你的 HTTPS 证书剩余不足 15 天直接发邮件或者 Webhook 通知。这样即使服务器内部定时任务出了岔子你也不会等到证书过期被用户发现才后知后觉。我自己后来就把这个监控接到了钉钉群里每天 9 点自动检查一次。5.3 关于 ISRG Root X1 的一个小提醒最后顺带提一个迁移后很可能遇到的坑尤其如果你的用户里有比较老旧的设备或者老版本的 Java 程序Lets Encrypt 的证书链依赖 ISRG Root X1 根证书。很多年前签发的证书链里最终端点不一定被旧设备信任。少数系统的内置根证书库没有更新 ISRG Root X1就会在 HTTPS 握手时报出“证书颁发者不受信任”的警告。这里的处理方式不是让 Lets Encrypt 背锅而是检查你的证书链文件里是否包含了R3中间证书以及目标客户端/设备的根证书库是否更新到可信任 ISRG Root X1 的版本。微信小程序、部分 Java 8 早先版本、旧款路由器管理页都可能遇到过这个信任链问题。迁移到新环境时顺便把链也检查一遍能省掉后面的一大堆解释工作。回到最初的问题Lets Encrypt 证书到底会不会自动续期我的答案是它会自动续但它只会在“续期环境完整”的前提下自动续。这个环境包括证书管理程序、定时任务、域名验证通道以及正确的权限和路径。服务器迁移这个动作本质上就是在拆散这个环境。所以每次迁移服务器把网站跑起来只是及格线真正的毕业标准是跑一次certbot renew --dry-run看到那行Congratulations, all simulated renewals succeeded。看到这四个英文单词的时候你才算真的把网站从旧服务器“续”到了新服务器。