Let’s Encrypt 证书续期失败,通常不是到期当天才出问题。
它往往早就失败过几次,只是你没有看到。等浏览器开始提示证书过期、用户打不开 HTTPS、支付回调失败、API 客户端报 TLS 错误时,故障已经变成线上事故。
更麻烦的是,很多 VPS 用户以为自己装了 certbot,系统里也有 certbot.timer,就等于自动续期一定没问题。实际上 timer 只代表它会定时运行,不代表每次续期都成功。
这篇按排查顺序讲:先确认有哪些证书,再 dry-run,接着查端口 80、Nginx、Cloudflare、DNS-01、deploy hook 和监控。
第一条命令:
sudo certbot certificates
重点看:
- Certificate Name
- Domains
- Expiry Date
- Certificate Path
- Key Type
如果这里显示证书还剩十几天甚至几天,就不要等自动续期“自己修好”。直接跑 dry-run。
sudo certbot renew --dry-run
Certbot 官方文档也建议用 dry-run 测试自动续期路径。它会走一遍模拟续期流程,不会替换真实证书,是最安全的第一诊断命令。
如果 dry-run 成功,至少说明当前验证方式、Web 服务器配合、DNS 或 challenge 基本没问题。如果失败,就按错误信息继续查。
先看 timer:
systemctl list-timers | grep certbot
systemctl status certbot.timer
再看日志:
journalctl -u certbot --since "7 days ago" --no-pager
journalctl -u certbot.timer --since "7 days ago" --no-pager
不同安装方式可能用 cron,也可能用 systemd timer。可以再查:
ls /etc/cron.d/
ls /etc/cron.daily/
常见误区是:timer active,但每次运行都失败;或者系统用的是 snap 版 certbot,日志位置和包管理器安装版不完全一样。不要只看“有没有 timer”,要看 dry-run 和日志。
Let’s Encrypt 的 HTTP-01 challenge 会访问:
http://你的域名/.well-known/acme-challenge/<TOKEN>
Let’s Encrypt 官方文档明确说明:HTTP-01 只能在端口 80 上完成。不能指定 8080、8888 或其他端口。
所以你要确认:
- 域名 A / AAAA 记录指向这台 VPS
- VPS 安全组 / 防火墙放行 80
- Nginx/Apache 监听 80
.well-known/acme-challenge/没被错误重定向或拦截- Cloudflare 代理没有改变验证路径
先从外部看 80 是否能访问:
curl -I http://example.com
再测试 challenge 路径。你可以临时创建一个测试文件:
sudo mkdir -p /var/www/html/.well-known/acme-challenge
printf 'ok\n' | sudo tee /var/www/html/.well-known/acme-challenge/test
curl http://example.com/.well-known/acme-challenge/test
如果返回 ok,说明这个路径至少能被公网访问。测试后删除:
sudo rm /var/www/html/.well-known/acme-challenge/test
如果访问不到,就继续查 Nginx 配置和防火墙。
很多网站会把 HTTP 强制跳转 HTTPS:
return 301 https://$host$request_uri;
这通常没问题,Let’s Encrypt 可以跟随一定次数的 HTTP/HTTPS 重定向。但如果你把 .well-known 转到错误目录、转到登录页、转到另一个域名、或者 CDN/WAF 返回 403,就会失败。
建议给 challenge 单独留路径:
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
改完先测试:
sudo nginx -t
sudo systemctl reload nginx
不要直接重启。nginx -t 先过,再 reload。
很多 VPS 网站套了 Cloudflare。Cloudflare 本身不是问题,但会让排查路径多一层。
常见坑:
- DNS 记录橙云代理后,验证请求先到 Cloudflare,再到源站
- 源站 80 端口没开,但 Cloudflare 缓存或规则让表面访问看起来正常
- WAF / Redirect Rule / Page Rule 影响了
/.well-known/acme-challenge/ - Cloudflare SSL 模式和源站证书状态混在一起排查
如果你用 HTTP-01,建议先确认源站 80 端口真实可达。必要时可以临时把相关记录切到 DNS only(灰云)测试,或者改用 DNS-01。
如果网站长期套 Cloudflare,通配符证书或不方便开放 80,DNS-01 往往更合适。
Let’s Encrypt 的 DNS-01 challenge 是在 DNS 里创建 TXT 记录:
_acme-challenge.example.com TXT "..."
它适合这些情况:
- 需要
*.example.com通配符证书 - 源站不方便开放 80
- 网站在 Cloudflare / 反代后面
- 多台服务器共用同一证书流程
但 DNS-01 的核心问题是传播时间。你本地看到 TXT 记录,不代表 Let’s Encrypt 从它的验证节点也能看到。
可以这样查:
dig TXT _acme-challenge.example.com @1.1.1.1
dig TXT _acme-challenge.example.com @8.8.8.8
也可以查权威 NS:
dig example.com NS +short
dig TXT _acme-challenge.example.com @ns1.example-dns.com
如果多个 nameserver 返回不一致,续期就可能随机失败。这个问题在部分 DNS 服务商、主从 DNS、手动 TXT 记录更新场景里很常见。
使用 DNS API token 时,要注意权限:
- 只给目标 zone 的 DNS edit 权限
- 不要把全局 API key 放在 Web 根目录
- 凭据文件权限设为 600
例如:
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
Let’s Encrypt 官方文档说明,DNS-01 可以签发通配符证书,HTTP-01 不行。
所以如果你要:
*.example.com
就不要继续纠结端口 80,应该走 DNS-01。
使用 Cloudflare 插件时,大致流程是:
sudo apt install python3-certbot-dns-cloudflare
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d example.com \
-d '*.example.com'
不同系统、snap 版 certbot、不同 DNS 插件包名可能不同。正式执行前先查你当前 certbot 的安装方式:
which certbot
certbot --version
还有一种情况:certbot 续期成功了,但 Nginx 没 reload,仍然拿着旧证书服务。
先看证书文件到期时间:
sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates
再看线上实际返回的证书:
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
如果本地文件是新的,线上还是旧的,就要 reload Web 服务。
sudo nginx -t
sudo systemctl reload nginx
更好的方式是设置 deploy hook,让续期成功后自动 reload:
sudo certbot renew --deploy-hook "systemctl reload nginx"
也可以把 hook 放进:
/etc/letsencrypt/renewal-hooks/deploy/
注意:deploy hook 只应该做轻量、确定的动作,例如 reload nginx。不要在里面写复杂部署脚本。
看到失败后,有些人会一直跑:
sudo certbot renew --force-renewal
这不是好习惯。Let’s Encrypt 有速率限制,反复失败可能让你短时间内无法继续签发。排查时优先用:
sudo certbot renew --dry-run
如果你需要频繁测试流程,可以使用 staging 环境,避免消耗正式额度:
sudo certbot certonly --staging -d example.com
真正修好配置后,再回到正式环境。
不要只依赖 certbot 自己。至少要有一个外部或本机提醒。
本机简单检查:
DOMAIN=example.com
END_DATE=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
END_TS=$(date -d "$END_DATE" +%s)
NOW_TS=$(date +%s)
DAYS_LEFT=$(( (END_TS - NOW_TS) / 86400 ))
echo "$DOMAIN certificate expires in $DAYS_LEFT days"
如果低于 14 天,就应该报警。生产环境更建议用外部监控,因为如果 VPS 自己 DNS、网络或任务调度出问题,本机脚本也可能一起失效。
遇到 Let’s Encrypt 续期失败,按这个顺序走:
-
sudo certbot certificates看证书和到期时间 -
sudo certbot renew --dry-run复现续期错误 -
systemctl list-timers | grep certbot看自动任务 -
journalctl -u certbot --since "7 days ago"看日志 - HTTP-01:确认 80 端口公网可达
- HTTP-01:确认
/.well-known/acme-challenge/能访问 - Nginx:
nginx -t后再 reload - Cloudflare:检查橙云、WAF、Redirect Rule、SSL 模式
- DNS-01:确认 TXT 在多个 resolver 和权威 NS 都能查到
- 通配符证书:使用 DNS-01,不要用 HTTP-01
- 续期成功但线上旧证书:检查 deploy hook / reload
- 不要反复
--force-renewal,优先 dry-run 和 staging - 设置 14 天到期提醒
证书续期失败不是玄学。大多数问题都落在四类:验证请求到不了、DNS TXT 没传播、续期成功但服务没 reload、没人监控到期时间。把 dry-run、challenge 路径、DNS-01 和 deploy hook 这几件事理顺,HTTPS 证书就不会每 90 天给你制造一次惊吓。
