HTTPS证书自动续期发生故障,详细故障排查记录
测试环境说明
服务器:LuckVM 香港节点;环境:Nginx 1.24 + Certbot 2.7.4;证书:Let's Encrypt 免费 SSL 证书
给网站装了 HTTPS 证书之后,Let's Encrypt 的免费证书只有 90 天有效期,很多人配置完自动续期就不管了,结果 3 个月之后证书过期,网站打开提示不安全,才发现自动续期失败了。我上周就遇到了这个问题,crontab 里明明写了续期任务,结果到期没续上,查了半个多小时日志才找到原因,这篇把常见的自动续期失败原因和排查方法记下来,避免大家踩同样的坑。
一、先手动测试续期能不能成功
遇到自动续期失败,第一步永远是先手动跑续期命令,看报错信息是什么,90%的问题手动跑一次就能找到原因:
# 测试续期,不真的续期,dry run模式
certbot renew --dry-run
如果手动dry run成功,说明证书本身配置没问题,问题出在自动任务上;如果手动跑都报错,就根据报错信息一个个解决,常见的手动报错有这几个:
报错1:Timeout during connect (likely firewall problem)
原因:防火墙没放行 80 端口,或者 Nginx/Apache 没启动,Certbot 的 HTTP-01 验证需要访问服务器 80 端口下的验证文件,端口不通就验证失败。解决:
- 检查 ufw/安全组有没有放行 80 端口:
ufw allow 80/tcp - 确认 Nginx/Apache 正在运行:
systemctl status nginx - 如果开了 CDN(比如 Cloudflare),续期的时候暂时把 CDN 调成「开发者模式」,或者暂停 CDN,不然验证请求会到 CDN 节点上,到不了你的服务器
报错2:Invalid response from xxx/.well-known/acme-challenge/
原因:网站根目录配置错了,或者伪静态规则拦截了验证请求,Certbot 找不到验证文件。解决:
- 确认 Nginx 配置里的 root 路径是正确的网站根目录
- 如果有伪静态规则,在规则里加一行,把 .well-known 目录排除,不让伪静态拦截:
location ^~ /.well-known/acme-challenge/ {
root /var/www/你的网站根目录;
allow all;
}
- 手动在网站根目录创建 .well-known/acme-challenge/test.txt 文件,浏览器访问
http://你的域名/.well-known/acme-challenge/test.txt,确认能正常打开,能打开就说明路径没问题
报错3:No valid IP addresses found for 域名
原因:域名 DNS 解析错了,或者解析到了 CDN/其他 IP,certbot 解析域名找不到你的服务器 IP。解决:检查域名 DNS 解析是不是指向你的服务器公网 IP,有没有开 CDN 代理(橙云),续期的时候可以暂时把 CDN 关了,续完再开。
二、自动续期任务没运行的排查
如果手动续期成功,但是到期没自动续,那就是定时任务 crontab 的问题,一步步查:
- 先看 Certbot 有没有自动加 systemd 定时任务,现在新版 Certbot 安装完会自动加定时任务,不用自己写 crontab:
systemctl list-timers | grep certbot
# 能看到 certbot.timer 就说明系统自带定时任务,每天会自动检查续期,一般是到期前 30 天自动续
# 查看定时任务状态
systemctl status certbot.timer
如果系统自带了 certbot.timer,就不用自己再写 crontab 了,两个定时任务同时跑反而可能出问题。
- 如果是自己写的 crontab 任务,先检查 crontab 有没有写错:
crontab -l
正确的续期任务应该是这样的(每天凌晨3点检查续期,续完重载Nginx):
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
⚠️ 常见crontab坑:
- 没用certbot的绝对路径:crontab的环境变量和你手动登录的shell不一样,直接写certbot renew会找不到命令,必须用全路径
/usr/bin/certbot,可以用which certbot看路径在哪 - 时间写错了:比如写的是
0 3 1 * *,那每个月1号才跑,要是证书到期不在1号就续不上,应该写每天都跑,certbot自己会判断需不需要续,没到期不会真的续 - 没加重载Nginx的post-hook:就算证书续成功了,Nginx还是用的旧证书,必须重载Nginx才会加载新证书,等于白续
- 跑任务的用户不对:crontab分用户,如果你是root用户写的任务,普通用户的crontab里不会有,要确认是root的crontab
- 看crontab日志,确认任务有没有运行:
grep CRON /var/log/syslog
# 看对应时间有没有certbot的运行日志,有没有报错
三、证书续了但网站还是显示旧证书
很多人续期成功了,但是浏览器打开还是提示证书过期,这是因为:
- 没重载Web服务:Nginx/Apache启动的时候会把证书加载到内存里,续完新证书存在磁盘上,必须重载Nginx才会重新读证书,所以续期命令一定要加
--post-hook "systemctl reload nginx" - CDN缓存了旧证书:如果开了Cloudflare或者其他CDN,CDN节点上缓存的还是旧证书,需要去CDN后台重新上传新证书,或者Purge CDN缓存
- 浏览器缓存了旧证书:按Ctrl+F5强制刷新页面,或者开无痕模式访问,排除本地缓存问题
四、我这次遇到的问题
这次我的证书续期失败原因特别蠢:之前开了 UFW 防火墙,默认拒绝入站,当时只放行了 443 和 SSH 端口,忘了放行 80 端口,Certbot 续期的时候用 HTTP-01 验证需要 80 端口,连不上就失败了。而且我写 crontab 的时候没加日志输出,任务跑失败了也没通知我,直到证书过期浏览器报警才发现。 解决方法:
- 放行80端口:
ufw allow 80/tcp - 在crontab里加日志,方便排错:
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx" >> /var/log/certbot-renew.log 2>&1
- 手动跑一次dry run确认成功,之后就正常了。
五、避坑总结
- 80 端口不要随便关,Let's Encrypt http-01 验证必须用 80 端口,就算你网站强制跳 HTTPS 也得开着 80
- 自动续期一定要加重载 Nginx/Apache 的钩子,不然续了也白续
- 定时任务要加日志输出,不然失败了根本不知道
- 不用自己写 crontab,新版 certbot 的 systemd timer 比自己写的 crontab 靠谱,默认安装就有,不会写错路径和参数
- 可以配置邮件通知,续期失败发邮件提醒,省得证书过期了才发现。
六、排查流程一图看懂
把上面的排查思路整理成一张流程图,遇到证书没续上的时候照着走一遍,基本能定位到问题:
七、常见问题与原因对照表
| 现象 | 最可能的原因 | 快速验证方式 |
|---|---|---|
Timeout during connect |
80 端口未放行、Web 服务未启动、CDN 拦截 | ufw status、systemctl status nginx |
Invalid response from .../.well-known/ |
网站根目录配置错误、伪静态规则拦截 | 浏览器访问 /.well-known/acme-challenge/test.txt |
No valid IP addresses found |
DNS 解析错误、解析到 CDN 节点 | dig 你的域名 对比服务器公网 IP |
| 手动续期成功但到期未续 | 定时任务未配置或配置错误 | systemctl list-timers | grep certbot、crontab -l |
| 续期成功但浏览器仍报过期 | 未重载 Web 服务、CDN 或浏览器缓存 | systemctl reload nginx 后强制刷新 |

浙公网安备 33010602011771号