
先说结论Lets Encrypt 证书确实会“自动续期”但这个“自动”不是指 CA 那边每天把新证书主动塞给你而是靠部署在服务器上的客户端程序最常见的是 certbot、acme.sh定期发起续期请求然后经过 ACME 验证流程最后把新证书写入服务器本地。很多人在服务器迁移之后发现证书过期了不是 Lets Encrypt 不“自动”而是迁移过程中把“发起续期”的整套机制弄丢了或者弄坏了。这篇文章不打算重复文档里已经写得很清楚的安装步骤而是从一次真实的迁移排查讲起服务器从 A 迁到 B80/443 端口都正常站点打开也没问题唯独证书到期那天浏览器开始报警。接下来我会带你把 Lets Encrypt 自动续期的完整链路拆开搞清楚迁移后哪些环节最容易坏以及怎么一步步定位和修复。适合自己维护云主机、用 Nginx 或 Docker 部署站点的人也适合刚被 Certbot 命令折磨过的新手。1. Lets Encrypt 的“自动续期”到底是谁在“自动”1.1 90 天证书的设计逻辑Lets Encrypt 把证书有效期定为 90 天而不是像很多商业证书那样一口气发一年甚至更久这看起来像是一刀切的决定背后其实是整套自动化的设计思路证书有效期越短就算某个环节出问题暴露面也越小同时短周期倒逼所有人在服务器上一次性配好自动续期不要总靠人工去记。90 天这个窗口并不是拍脑袋它既给运维留出了足够的容错时间又足够频繁地把“续期”这个动作训练成服务器上的常规任务。打个比方商业证书一年有效期有点像“一年换一次门锁”就算钥匙丢了你有一整年时间慢慢配中间出了安全事故也不会立刻暴露。而 90 天证书更像是“三个月换一次密码”它逼你养成习惯但真正的安全性其实来自一套自动换密码的流程而不是密码本身。理解了这层逻辑你就明白为什么 Lets Encrypt 从设计上就把“自动化”当成了默认要求而不是可选优化。1.2 常见的续期定时任务长什么样“自动续期”落地到服务器上通常是一个定时任务最常见的有几种新版 Ubuntu 上通过 snap 安装 certbot会生成一个 systemd timer名称通常包含certbot.timer或snap.certbot.renew.timer定时触发certbot renew。如果通过 apt 或 yum 安装会在/etc/cron.d/certbot里有一条 cron 条目内容大致是每天执行certbot renew --quiet。用 acme.sh 的人安装完成后会自动写入一条 cron 任务每天跑到~/.acme.sh/acme.sh --cron。用 Caddy 这类自带 HTTPS 的软件则会直接在进程内部发起续期不依赖系统定时任务。每次定时任务被触发后客户端并不会简单粗暴地重新申请证书而是先检查现有证书还剩多少天。certbot 的默认逻辑是剩余有效期大于 30 天就不动作小于 30 天才真正开始续期。所以你在日志里经常看到当天任务正常执行了但证书没变这是对的不是故障。1.3 “自动续期”生效的三个前提我把自动续期的链路归纳成三个环节第一定时任务还能被系统正常触发。不管你是用 cron 还是 systemd timer都得确认它确实在运行而且能调用到正确的 certbot 或 acme.sh 可执行文件。第二证书客户端还能读取到原有的账号、私钥和配置。这里的重点不只是live/目录下的证书文件还包括accounts/目录里的 ACME 注册账号以及renewal/目录下的续期配置。这些文件一旦丢失客户端会把自己当成一台全新的机器无法关联到原来的证书记录。第三域名解析、端口和 Web 服务器都能让 ACME 验证请求正常走通。Lets Encrypt 的验证请求必须从公网访问到你的服务器涉及 DNS 是否解析到新 IP、80 或 443 端口是否放行、Web 服务器是否正确响应挑战路径这些因素缺一不可。这三个前提任何一个断了表面上系统看起来完全正常可等到 90 天期满证书照样会死给你看。后面排查的大多数问题都逃不出这三条。2. 服务器迁移之后续期任务为什么会失效2.1 最容易丢的不是证书而是“续期入口”迁移服务器时很多人会做几件事打包网站目录、导出数据库、把 Nginx 配置原样拷过去却很容易忽略系统级的定时任务。我遇到的情况就是这样老服务器用的是 systemd timer搬去新服务器时我只是简单安装了 certbot但从来没有在迁移清单里提到“同步定时任务”。结果就是新服务器上连certbot命令都能用certbot certificates也能看到证书但没有任何东西在每天触发它。证书就像被丢在一个无人维护的区域没有提醒也没有修复等到浏览器弹警告的时候已经晚了。这个坑尤其容易出现在“重装系统”式迁移中。如果你是从一台真实的物理服务器迁到新机器又或者从一个系统版本迁到另一个很多系统级的服务并不会自动跟着你的业务目录一起走。定时任务属于基础设施的一部分既不在网站目录里也不在 Web 服务器配置里特别容易成为迁移漏网之鱼。2.2 路径、权限、环境变量还有一类坑藏在路径和权限里。如果你直接从旧机器上打包/etc/letsencrypt/整个目录拷到新机器然后手动解包文件的属主或权限很容易出问题。certbot 对私钥目录权限很敏感如果你把文件解压成普通用户可读或者目录属主变成了迁移工具账号那么定时任务一旦以 root 身份执行renew -q可能会因为权限异常直接退出连报错都只进系统邮件不显示在终端里。acme.sh 的用户也类似迁移时如果只拷贝了最后生成的证书文件而忘了整个~/.acme.sh目录等于把账号、私钥和续期配置全部丢掉之后只能重新注册账号签发。更隐蔽的问题是环境变量。有些场景里acme.sh 或 certbot 的 DNS 插件依赖 API Token 之类的环境变量而迁移后你只复制了脚本没有在新服务器的 shell 配置文件里补上环境变量。定时任务执行时用的环境又是一个非常精简的环境根本读不到你平时手动登录时设置的内容所以手动测试正常一放到定时任务里就失败。2.3 验证请求不通80 端口 / 域名解析 / 防火墙迁移之后另一个高频问题是证书文件还在但 ACME 验证请求已经没法走通了。Lets Encrypt 默认的 HTTP-01 验证会要求 Lets Encrypt 的服务器通过 80 端口访问你域名下的/.well-known/acme-challenge/路径目标是拿到一个随机 token 文件。假设你把 Web 服务迁到新机器但域名解析还没来得及完全切过来或者新服务器的 80 端口被云厂商安全组挡住又或者 Nginx 配置里没有把/.well-known/acme-challenge/这个前缀指向正确的目录那每次续期尝试都会卡在“授权失败”这一步。更难受的是这类问题不会在你的浏览器里直接暴露因为 443 端口正常证书没过期之前一切看起来风平浪静直到某天续期失败被反复重试最终证书过期才集中爆发。顺带一提有些 Web 服务器配置喜欢把全部 HTTP 请求统一 301 跳转到 HTTPS这本身没问题但如果跳转规则把/.well-known/acme-challenge/也覆盖了acme 客户端就会拿到一个跳转响应而不是文件内容最终验证失败。这种问题和防火墙有类似的表现但排查方向完全不同一个是看网络路径一个是看服务器配置。2.4 编排工具带来的“系统差异”如果你是用 Docker 或者 docker-compose 部署迁移后还很容易出现另一个矛盾以前证书是由宿主机上的 certbot 定时任务管理的新环境里却把整个服务塞进容器只在启动容器时挂载了证书文件目录没有挂载 ACME account 和 renewal 配置也没有在容器内设置任何定时任务。这个差异往往不报错但你仔细去看容器里根本没有“续期”这个东西。要解决也不难就看你是愿意把定时任务留在宿主机还是直接在容器旁边再跑一个专门的续期容器但前提是你得先意识到“任务入口已经换了位置”。如果你从一个纯手工管理的服务器迁到一个重度容器化的环境这一步尤其容易忽略因为迁移时你会觉得挂载证书文件路径就已经万事大吉了实际上续期机制还留在旧环境里没搬走。3. 实际排查从服务器迁移后的一串报错开始3.1 第一步搞清楚证书现在到底什么状态我迁移后的排查是从浏览器报错开始的。先用curl -v https://example.com/看一眼握手过程它会直接打印出证书的开始和结束时间。更准确的方法是直接查看服务器上的证书文件sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem然后再看这套 certbot 到底托管了哪些域名sudo certbot certificates这两条命令能看到两个关键信息当前证书文件是哪天到期这台机器上到底有哪些证书是被 certbot 管理的。如果certbot certificates输出的证书清单为空说明这套 certbot 实例和当前正在使用的证书没有关联很可能配置文件或证书目录不是同一套。别小看这个问题迁移时很容易出现“Nginx 引用的证书来自某个目录而 certbot 管理的证书在另一个目录”这种错位。3.2 第二步翻出续期任务和日志确认证书状态后接下来要回答的是这台机器上到底有没有人负责续期。如果你是用 systemd timer 管理先看 timer 有没有在跑systemctl list-timers | grep -i certbot systemctl status certbot.timer如果你是用 cron也别忘了去看一看cat /etc/cron.d/certbot crontab -l如果定时任务都在接着去翻日志。certbot 会把每次运行写到/var/log/letsencrypt/letsencrypt.log。用journalctl -u certbot.timer只能看到定时任务本身是不是被触发真正想看续期过程有没有成功必须看这个日志文件。我在迁移后第一次看日志时发现日志最后一条记录的日期还是旧机器上的时间也就是说新机器上的 certbot 从迁移至今根本就没跑过一次。这个发现比看到一堆报错信息还要高效因为它能直接把排查方向引向“定时任务缺失”而不是急着去看证书内容。如果你连/var/log/letsencrypt/这个目录都没看到那基本可以确定 certbot 在这个环境里从来没正常执行过续期。3.3 第三步手动模拟续期让报错现形如果日志里有多次运行记录但证书还是过期了那就需要手动执行一次续期让真实错误直接暴露在终端里。推荐先跑sudo certbot renew --dry-run--dry-run不会实际生成证书但会完整走一遍 ACME 流程包括注册、验证和签名请求所以它能很清楚地让你看到当前续期链路到底卡在哪一步。如果这条命令不报错那说明环境基本是通的问题反而可能出在“自动触发”那边如果报错认真看错误类型Invalid response from ...是验证请求没被 Web 服务器正确处理。No renewal configuration found是 certbot 管理清单里根本没有这个域名。API rate limit是短时间内重复签发太多次被限流。Permission denied多半是目录权限或安装方式的问题。为了进一步确认你还可以临时在 Web 服务器根目录下放一个测试文件再用本机 curl 试一下验证路径echo test /var/www/html/.well-known/acme-challenge/probe curl -I http://example.com/.well-known/acme-challenge/probe如果返回 200 说明路径是通的如果返回 404 或 403就要回头检查 Nginx 配置、站点 root 目录是否正确以及有没有把 acme-challenge 这个前缀误写进重定向规则。3.4 第四步定位验证链路断在哪里验证链路是最容易出幺蛾子的地方我遇到过三种非常典型的情况。第一种云服务商的安全组把 80 端口关了只放行 443这种情况从服务器本地 curl 是通的但从外面测试就 404因为请求根本进不了主机。第二种Nginx 配置里把整个 location 都加了强制跳转所有 HTTP 请求都 301 到 HTTPS结果 ACME 挑战请求也被跳走了客户端拿不到 token 文件。第三种是迁移后域名解析还没完全切干净ACME 请求打到了旧的服务器 IP 上旧机器上已经没有这个验证路径自然验证失败。这些都可以通过观察日志和测试请求来区分。在这个阶段最忌讳的是跳过诊断直接删掉证书重新签发。直接删证书虽然爽快但很容易把本来还能用的私钥和签发记录也一并丢掉一旦连续申请次数过多还会触发限流。先搞清楚链路哪里断再决定怎么修。4. 续期失灵后的两种恢复方案4.1 证书还没过期恢复续期配置如果证书还活着只是快到期了那你要做的是恢复续期机制而不是急着换证书。先把旧机器上 certbot 的管理目录完整搬家。certbot 的配置都集中在/etc/letsencrypt/里其中live/是当前使用的证书软链接archive/是历次签发的证书备份renewal/是每个域名的续期配置accounts/是你在 Lets Encrypt 注册的账号私钥。这四个目录在续期时各有用处缺一个都可能导致将来出问题。用 tar 整体打包最省事sudo tar czf le-bak.tar.gz /etc/letsencrypt然后在新服务器上解压并把目录属主和权限恢复成 root私钥文件权限保持 600。之后sudo certbot renew --dry-run测试新机器上的续期环境是否正常。之后再看 timer 或 cron 是否已经注册。如果新机器是通过 snap 安装的 certbotsystemd timer 一般会自动创建但如果你是从旧机器拷配置、另一边又用二进制手动安装这两者未必能对上最好重新确认一遍。确认无误后再跑一次sudo certbot renew会真正把快过期的证书更新掉。整个过程不需要删掉原有证书也不需要重新跑注册命令。4.2 证书已经过期重新签发并修复定时任务如果证书已经彻底过期浏览器已经报警那就不用纠结续期了直接重新签发一套。但也不要一上来就删目录优先试试sudo certbot renew --force-renewal让 certbot 基于原有配置重新生成证书。如果这个命令报错说找不到续期配置或者你舍不得旧配置里一堆历史文件这时再把对应域名的证书删掉重新申请sudo certbot delete --cert-name example.com sudo certbot certonly --webroot -w /var/www/html -d example.com --email youremail.com --agree-tos --no-eff-email用 webroot 插件的时候一定要保证 ACME 验证路径确实能被 Lets Encrypt 服务器访问到也就是前面说的 80 端口和 Web 服务器对/.well-known/acme-challenge/的响应都正常。如果申请的是泛域名证书比如*.example.com那就得走 DNS-01 验证这时需要把 DNS 服务商的 API 凭证一并迁到新机器并按 certbot 插件要求放在权限为 600 的配置文件里。重新签发完成后不要忘记回到第一件事确认定时任务存在。如果新机器上仍然没有 systemd timer 或 cron 条目哪怕这次证书签发成功三个月后你又会面临同样的过期问题。4.3 迁移时让续期不丢的三个动作经历过这次事情之后我把“证书续期机制”正式写进了迁移清单。第一迁移前先在旧机器上执行sudo certbot renew --dry-run确认旧环境是正常的然后再开始打包操作。这一步能提前暴露旧机器是否已经在静默失败避免你把一个有问题的环境原样搬到新机器。第二迁移清单里除了网站目录和 Web 服务器配置之外必须有 systemd service/cron 配置这一项把定时任务名称、执行命令、运行用户都记录下来。这个条目虽然不起眼但缺了它续期就是无源之水。第三新机器部署完成后尽快执行续期干跑最好在对外切换 DNS 之前就把验证路径测通这样即使验证出问题也还有时间调。这三步看着简单却能避免绝大多数“迁移后半年突然证书挂了”的坑。5. 常见问题与避坑清单5.1 一张表掌握迁移后证书排查为了让你在浏览器报错时少走弯路我把迁移后最常踩的几类问题整理成速查表。现象常见原因排查方法解决办法日志里没有任何续期记录systemd timer 或 cron 没迁移systemctl list-timerscat /etc/cron.d/certbot补上定时任务或重装 certbot 定时器手动续期提示No renewal configuration found只复制了 live 目录没复制 renewal 和 accountssudo certbot certificates从旧机器整体拷贝/etc/letsencrypt续期报Invalid response80 端口没开或 acme-challenge 路径被跳转curl -I http://域名/.well-known/acme-challenge/probe放行 80 端口取消对挑战路径的强制跳转证书显示未过期但浏览器报警Web 服务器引用的证书不是 certbot 管理的那个nginx -T检查ssl_certificate路径把引用路径统一到 certbot 输出目录泛域名证书无法续期DNS API 凭证没有同步查看/etc/letsencrypt/renewal/*.conf同步 DNS 插件配置文件并把权限改为 600一切命令正常但证书还是过期系统时间不准确timedatectl statusdate配置时间同步服务最后一行虽然少见但我在一次排查中真实遇到过。系统时间如果偏移太多certbot 计算证书剩余有效期时会出现偏差导致它误判为“还没到期”从而跳过续期。这种事不常发生可一旦踩中排查难度极高所以也列出来提醒一句。5.2 我不再犯的几类操作认知第一类认知是“反正有自动续期所以我可以不管”。自动续期不是 CA 单方面提供的服务它需要服务器这边所有环节都活着。把它当成“任务验证存储”这三层来理解你才不会被表面的自动化骗了。第二类认知是“证书过期了直接删掉重新签发就对了”。频繁删除再申请一方面会让旧证书立刻无法使用另一方面如果同一域名被短时间反复签发会触发 Lets Encrypt 的限流机制到时候你连签发新证书的资格都没有。只要旧配置还能用优先续期而不是重签。第三类认知是“迁移就是拷贝文件”。系统服务、定时任务、账户私钥这些不在文件列表里看得见的部分恰恰是自动化依赖的核心。每次迁移前都问自己一句这台机器上除了业务数据还有哪些后台任务在跑有没有定时任务有没有服务注册把这些都列出来才算真正把服务器搬干净了。5.3 一个更省心的长期方案如果你不想每次都在迁移时折腾 certbot 这套复杂状态可以尝试把续期逻辑和证书存储尽量解耦比如用 acme.sh 的 DNS API 模式配合独立存储目录或者直接用支持自动 HTTPS 的服务器软件。减少对外部配置的依赖迁移时真正需要带走的文件也就越少。我个人比较喜欢给证书目录加一个独立备份任务每次续期成功后自动把/etc/letsencrypt或~/.acme.sh打成压缩包传到另一个存储位置这样无论服务器怎么迁移手头永远有一份完整的可移植状态。再配合一个简单的到期提醒脚本提前 30 天把即将过期的域名列进待办事项基本能把意外过期这件事从日常运维里彻底摘除。最后再分享我自己的一个习惯每次迁完服务器我都会在浏览器里逐个打开以前配置过的域名确认证书有效然后在当天的记录里写上“证书续期检查完成”。听起来原始但这份人工复核在关键时候比任何监控后台都可靠。Lets Encrypt 的 90 天有效期本来就是为了逼我们养成自动化习惯可自动化并不等于丢在那里不管。把“自动续期”当成一个有生命的系统去维护定期看日志、定期干跑、定期备份你才能真正睡个好觉。