
讲一个我最近实际碰到的事做服务器迁移机器从旧机房搬到新环境域名解析、Nginx配置、数据库都还原得好好的结果一周后监控报警——站点证书过期。我当时第一反应就是“Let‘s Encrypt不是会自动续期吗怎么换个机器就不续了”紧接着打开浏览器满屏的红色警告页用户反馈已经进来了。这篇文章就从这次“迁移后证书失效”的排查出发把Let’s Encrypt证书自动续期的真实机制、迁移后为什么容易断掉、以及我验证过的一整套排查和恢复流程完整记录下来。不管你是刚接触证书的小白还是被类似故障折腾过的运维这篇内容应该都能对得上号。1. 先搞清楚Let‘s Encrypt的“自动续期”到底是怎么自动的很多人的认知是“Let’s Encrypt证书会自动续期”这个说法没错但容易让人误解成“证书到期前系统会自动帮你换好什么都不用管”。实际情况是自动续期依赖一个部署在服务器本地的客户端程序比如最常见的Certbot它在系统里注册了一个定时任务到时间就会去检查证书剩余有效期快到期就执行续期。1.1 自动续期的执行链路Let‘s Encrypt证书的有效期是90天这是由CA设定的策略原因主要是用短有效期降低私钥泄露造成的风险窗口也倒逼用户自动化管理证书。所以续期这件事不是Let’s Encrypt中心服务器主动发起而是你的服务器主动找它要新证书。流程大致是定时任务被触发Certbot用的是systemd timer或crontab取决于安装方式和系统版本客户端读取/etc/letsencrypt/renewal/下的续期配置客户端判断哪些证书剩余天数小于30天默认阈值通过ACME协议访问Let‘s Encrypt的API完成域名所有权验证验证通过后签发新证书替换旧证书调用deploy-hook之类的后置动作重载Nginx或Apache上面这串链路任何一环断了续期就静默失败。最麻烦的就是“静默失败”——定时任务跑过了但什么都没做成日志里有报错可没人天天翻日志。1.2 迁移服务器时最容易被忽略的前提条件续期成功有几个硬性前提。第一服务器必须能正常访问Let’s Encrypt的CA服务器也就是能连通外网至少80端口或443端口要能出站。第二用来验证域名所有权的挑战请求要能到达你的服务器HTTP-01验证方式要求外界能访问到http://你的域名/.well-known/acme-challenge/这个路径。这两条在新环境里经常出问题下面会细说。我当时迁移的时候以为把文件拷过去就万事大吉了结果恰恰是“自动续期依赖的本地运行环境”没有完整还原。2. 服务器迁移哪些东西丢失会让续期直接失效服务器迁移不是一个“复制粘贴”的动作它牵扯到你原有环境里的所有状态。Let‘s Encrypt续期又是一个强依赖本地状态的流程所以迁移后证书不续期的概率其实非常高。2.1 最容易搞丢的/etc/letsencrypt 目录这个目录是Certbot的家里面存着你的账户私钥、已签发证书、续期配置。迁移时如果只备份了网站根目录和Nginx配置大概率会把这个目录漏掉。丢失之后会出现两种情况新机器上跑certbot certificates直接提示找不到任何证书或者你手动重新申请Certbot会生成一套全新的账户信息之前签发的证书和密钥对不上我当时迁移时特意打了包整个/etc/letsencrypt但还是在另一台机器上栽过跟头因为那台机器上装的是旧版Certbot新版读不了旧版的续期配置一跑就报解析错误。后面会讲到怎么处理新旧版本兼容问题。2.2 定时任务没跟着走有些人迁移后根本不装Certbot直接把旧机器上的/etc/letsencrypt目录拷到新机器然后证书确实在网站也正常但新机器上根本没有安装certbot也没有注册systemd timer。而很多系统里certbot的定时任务是装在/etc/cron.d/certbot或/usr/lib/systemd/system/certbot.timer里的这些文件不在站点目录里漏掉非常正常。怎么确认定时任务在不在后面排查环节会给出具体命令。这里先说结论迁移后第一个要检查的不是证书本身而是续期的“调度器”有没有跟过来。2.3 域名解析和防火墙环境变了迁移后往往伴随IP更换和DNS切换。如果新IP的DNS解析还没生效或者新机器防火墙没放行80/443端口ACME验证请求就进不来。Let‘s Encrypt的HTTP-01验证是CA那边发起到你域名的请求不是你自己访问自己所以即使本机看起来“网络通”外部访问不到也没用。我在这次迁移中遇到过很典型的坑新服务器的Nginx默认配置占用了80端口但只监听了IPv6ACME挑战走IPv4压根进不去。这种问题不抓包基本看不出来。3. 完整排查过程从监控告警到定位根因这次故障我花了大半天走了不少弯路。下面把排查的最小流程从头到尾捋一遍照着做大概率能快速定位。3.1 第一步确认证书到底过期没有不要凭浏览器提示判断直接用openssl看服务端实际加载的证书。# 查看某域名实际签发的证书详情包括过期时间 echo | openssl s_client -servername example.com -connect example.com:443 2/dev/null | openssl x509 -noout -issuer -subject -dates # 直接在服务器本地查看证书文件 openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates我用第一条命令查的时候发现证书过期时间就是当天而且签发者是对的。这就说明证书本身没问题问题是到期了没换。判断是不是Let‘s Encrypt签发的看issuer字段就能确认。我见过有人把isrg root x1当成山寨证书其实那是Let’s Encrypt的根证书名称正常得很。3.2 第二步查看certbot的日志和证书列表certbot certificates # 输出里会列出所有托管证书、域名、到期时间、续期配置路径 tail -n 100 /var/log/letsencrypt/letsencrypt.log日志这一步特别重要。certbot每次被定时任务触发都会在日志里留下痕迹。我当时看到日志末尾有一行“Cert is due for renewal, auto-renewing...”然后紧跟着“Failed to renew certificate...”说明定时任务其实一直在跑只是在续期那一步失败了。如果连日志都没有说明定时任务压根没触发问题出在调度器或certbot没安装。3.3 第三步检查定时任务是否注册不同系统检查方法不一样两个命令都跑一下总有一个有结果# 检查systemd timer systemctl list-timers | grep certbot systemctl status certbot.timer # 检查crontab cat /etc/cron.d/certbot crontab -l注意我之前遇到过一种情况systemd timer虽然是enable状态但这台机器根本没跑systemd比如某些容器环境定时任务自然永远不会触发。迁移到这个环境之前用crontab的配置就丢了。3.4 第四步手动执行续期复现故障certbot renew --dry-rundry-run是只做演练、不实际替换证书的安全模式。它会完整走一遍ACME验证流程但不会把新证书写进配置文件。如果这里报错报错信息基本就是根因。我当时dry-run时报的错很经典请求到Let‘s Encrypt的验证服务器时连接被拒。随后我用curl -I http://example.com/.well-known/acme-challenge/test测试发现本机访问正常但换到外部网络访问就超时。最后排查半天确认是新环境里iptables规则把来自特定IP段的请求拦了。3.5 第五步确认续期后是否正确重载服务就算证书真的续期成功了如果Nginx没重载对外提供的还是旧证书。certbot默认会尝试运行服务的reload命令但有时候因为服务名不对reload不会执行。我见过有人certbot renew输出显示成功浏览器还是红色报错最后发现Nginx进程缓存了旧证书。这里有个小技巧续期之后看证书文件的时间戳有没有变化ls -l /etc/letsencrypt/live/example.com/fullchain.pemlive目录下其实是指向archive目录的软链接如果续期成功且软链接更新时间戳会指向新文件。4. 迁移后的快速恢复方案定位到问题后接下来是修复。下面是我经过几次折腾后总结出的迁移恢复清单照着走基本不会漏。4.1 保留配置迁移不要从零重新申请如果你迁移前已经备份了/etc/letsencrypt目录那就直接把这整个目录恢复到新机器对应路径然后重新安装同样版本的certbot。# 恢复到新机器后先检查目录权限 ls -l /etc/letsencrypt # 确认live目录下软链接没有断 ls -l /etc/letsencrypt/live/example.com/恢复完目录后跑certbot certificates能看到原有的证书信息就说明读取成功。接着注册定时任务systemctl enable certbot.timer systemctl start certbot.timer如果你用的是Debian/Ubuntu且装过python3-certbot-nginx或python3-certbot-apache插件certbot包本身会自动创建timer但迁移时这些服务可能没装全上面的命令就很有必要。4.2 来不及备份直接重新签发并挂到站点如果你没备份/etc/letsencrypt那最干净的做法就是重新申请一个新证书然后替换到Web服务器配置里。# 以Nginx为例certbot会自动修改配置文件 certbot --nginx -d example.com -d www.example.com # 只签发证书不自动改配置 certbot certonly --nginx -d example.com注意如果你原来的证书关联了多个域名需要在-d后面全部列出来漏一个就等于少签一个。如果域名很多建议直接--nginx让certbot从现有配置里自动发现域名。4.3 续期配置缺失时的处理一种让人头大的情况是证书文件和密钥文件都在/etc/letsencrypt/live/下也有完整记录但/etc/letsencrypt/renewal/下没有对应的续期配置文件。这种情况下certbot虽然能看到证书但不会自动续期因为续期配置文件丢了。解决办法是重新生成手动创建一条续期配置太麻烦最省事的是重新跑一次申请命令覆盖原证书。certbot certonly --force-renewal会强制重新签发并自动生成新的续期配置。这里不需要太担心重复签发Let‘s Encrypt对相同域名的证书数量有速率限制但正常操作频率下不会触发。4.4 验证修复是否成功修复完不要急着走把验证流程跑一遍certbot renew --dry-run systemctl list-timers | grep certbot openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates如果dry-run成功说明自动续期的链路已经通了。再实测一次把证书剩余天数改成测试值或者干脆等到快到期定时任务就会自动触发。想手动验证完整流程可以执行certbot renew --force-renewal --deploy-hook systemctl reload nginx这个命令会真正执行一次续期并且用deploy-hook重载Nginx。验证完成后新证书应该立刻生效。5. 常见问题速查与避坑经验这几年折腾Let‘s Encrypt我把高频问题收敛成了一张速查表基本覆盖了迁移场景和日常运维场景里99%的状况。问题现象可能原因解决办法certbot证书列表为空但live目录有证书renewal配置丢失用force-renewal重新签发或重建renewal配置定时任务存在但日志为空certbot版本不兼容或lib缺失卸载重装certbot恢复原版本续期提示“验证超时”防火墙拦截或Nginx配置问题检查80端口访问确认challenge路径可达续期成功但站点仍显示旧证书Web服务器未reload手动nginx -t systemctl reload nginx迁移后域名解析还没生效DNS TTL未过等解析生效或临时用--preferred-challenges dns浏览器报“证书链不完整”缺少中间证书确认fullchain.pem是否完整或用certbot自动配置老版本Java客户端不信任证书本地信任库没有ISRG Root X1更新JDK版本或手动导入Let‘s Encrypt根证书5.1 我踩过的几个典型坑第一个坑Debian 10上默认装的是certbot 0.x后来迁移到Debian 12重新安装了系统自带certbot结果版本升级后之前的配置格式不被识别直接certbot certificates都跑不了。后来我把旧机器上的certbot版本也升到一致再恢复目录才解决。所以迁移Let’s Encrypt证书时certbot版本一致性比想象中重要。第二个坑续期验证完全正常但浏览器还是提示NET::ERR_CERT_AUTHORITY_INVALID。当时我以为是证书链问题后来发现是网关设备在中间做了SSL解密把公网证书换成了内网根签发的假证书。这种情况在办公网络环境里特别常见排查时要懂得区分“服务器端问题”和“客户端网络环境问题”。第三个坑域名走了CDN或反向代理时ACME验证请求被CDN缓存挡住。Let‘s Encrypt的验证请求直接访问源站但HTTP-01挑战要求/.well-known/acme-challenge/请求必须到源站而CDN节点拦截了这条路径。这种情况最好用DNS-01验证方式通过DNS记录来完成挑战不需要80端口。5.2 迁移场景下的若干建议迁移前在旧机器上先执行一次certbot renew --dry-run确认旧环境一切正常。这样做的好处是如果迁移后出现问题至少能排除“旧环境本来就有毛病”。迁移时打包好这几样东西/etc/letsencrypt整个目录/etc/nginx或/etc/apache2等Web服务器配置/etc/cron.d/certbot或对应的systemd unit文件网站根目录下可能存在的/.well-known/acme-challenge/相关配置迁移后第一时间执行完整验证流程不要等监控报警。6. 顺手聊聊证书续期和根证书的兼容性证书续期本质上只是换一张“盖章的新纸”但“章”的信任链长什么样决定了一张证书在客户端眼里是否正规。Let‘s Encrypt在这个问题上有一套比较细的设计从早年是用DST Root CA X3交叉签名到现在的ISRG Root X1作为信任锚整个演变过程中老旧设备踩的坑并不少。比如Java程序连接你的HTTPS接口时低版本JDK内置的信任库可能没有ISRG Root X1于是报“unable to find valid certification path to requested target”。问题不一定出在证书自动续期上而是出在客户端内置根证书列表太旧。所以迁移服务器时如果同时升级了后端服务或JDK版本这类兼容性问题会突然冒出来容易被误判成证书挂了。我的建议是不要把“证书能签下来”当成全部还要关心“签下来的证书在目标客户端那里能不能被信任链验明白”。尤其涉及老系统、嵌入式设备、老Java应用时尽量在测试环境把不同客户端场景都验证一遍。6.1 关于复用的一个想法Let‘s Encrypt证书的自动续期本质上是一个可重复的工程化流程。迁移不是一次性的“搬完就没事”它要求你把原有环境的所有自动化能力也一起搬过去。所以这次排查完之后我把证书续期相关检查写进了一个简单的健康检查脚本每天跑一次用OpenSSL解析证书过期时间提前两周在群里发提醒。这套东西不复杂但确实让之后几次迁移都安心了不少。具体脚本逻辑也很简单读域名列表逐个取证书过期时间算出剩余天数小于阈值就输出警示。如果直接把这条命令写进crontab也能用但我觉得更多人需要的是一个检查思维而不是一段僵硬的代码——毕竟每台机器的证书路径、服务名称都不一样。最后分享一点排查经验我个人在实际操作中体会最深的一点是Let’s Encrypt自动续期失效90%的情况不是Let‘s Encrypt那边出问题而是本地环境变了。证书文件可能还在、网站还能访问但后台的续期“引擎”已经悄悄不转了。所以迁移后别急着把旧机器下线。旧机器上至少保留完整的原环境直到新机器第一次自动续期成功。这是我用“一次半夜紧急换证书”的代价换来的教训。如果早一步意识到“定时任务跟着机器走证书目录也要跟着走”那次故障完全能避免。证书技术本身并不复杂但它的坑往往藏在“你以为它正常”的地方。希望这篇记录能帮你在下次迁移时少走点弯路。