Let's Encrypt 证书续期失败:修复指南
Let's Encrypt 证书有效期仅为 90 天,需要定期自动续期。当 certbot renew 静默失败时,证书将在到期后导致浏览器显示"不安全"警告,用户无法通过 HTTPS 访问网站。如果使用 Cloudflare 的"完全(严格)"SSL 模式,源站证书过期还会导致 526 错误。
续期失败的原因通常很明确:端口 80 被占用、DNS 记录不正确、webroot 路径配置错误、或 Let's Encrypt 的 HTTP-01 验证请求无法到达您的服务器。本文将引导您逐步排查并修复这些问题,同时设置可靠的自动续期监控机制。
步骤 1:检查续期错误日志
首先查看 Certbot 的日志文件,了解续期失败的具体原因。日志位于 /var/log/letsencrypt/ 目录下:
# 查看最新的续期日志
sudo tail -100 /var/log/letsencrypt/letsencrypt.log
# 查看所有续期记录的历史
sudo ls -lt /var/log/letsencrypt/ | head -10
# 手动触发一次试运行(不会真正续期)
sudo certbot renew --dry-run
# 查看当前所有证书的状态和到期时间
sudo certbot certificates
在日志中查找关键错误信息。常见的错误包括:Problem binding to port 80(端口被占用)、Connection refused(验证请求无法到达服务器)、DNS problem: NXDOMAIN(DNS 记录不存在)、以及 Failed authorization procedure(验证失败)。根据不同的错误信息,选择对应的修复步骤。
步骤 2:修复端口 80 冲突
如果使用 standalone 模式续期,Certbot 需要临时绑定 80 端口来完成 HTTP-01 验证。但如果 Nginx 或 Apache 正在运行并占用 80 端口,续期就会失败:
# 检查端口 80 占用情况
sudo ss -tlnp | grep ':80'
# 方案 A:续期前临时停止 Web 服务器
sudo systemctl stop nginx
sudo certbot renew
sudo systemctl start nginx
# 方案 B(推荐):使用 webroot 模式,无需停止 Web 服务
sudo certbot certonly --webroot -w /var/www/html \
-d example.com -d www.example.com
方案 B 是生产环境的最佳实践。使用 webroot 模式时,Certbot 会将验证文件写入 Web 服务器的文档根目录,Let's Encrypt 通过 HTTP 请求访问该文件完成验证,全程无需停止 Web 服务。
如果您使用 Nginx,Certbot 可以自动配置 Nginx 插件来完成验证:
# 使用 Nginx 插件(推荐)
sudo certbot --nginx -d example.com -d www.example.com
# 修改已有证书的续期方式
sudo certbot renew --nginx
步骤 3:验证 DNS 配置
Let's Encrypt 的 HTTP-01 验证会向您的域名发起 HTTP 请求。如果 DNS 记录指向了错误的 IP 地址,验证请求将无法到达您的服务器,导致续期失败:
# 检查域名解析的 IP 地址
dig +short example.com
dig +short www.example.com
# 对比服务器公网 IP
curl -s ifconfig.me
# 如果使用 Cloudflare 代理(橙色云朵),验证请求
# 会先到达 Cloudflare 再转发到源站
# 确保 Cloudflare 的 SSL 模式设为"完全"或"完全(严格)"
# 而非"灵活"模式,否则可能导致验证失败
如果 DNS 记录指向的 IP 与服务器实际 IP 不一致,更新 DNS 记录后等待传播完成(通常几分钟到几小时),再重新尝试续期。使用以下命令检查 DNS 传播状态:
# 使用公共 DNS 检查解析结果
dig +short example.com @8.8.8.8
dig +short example.com @1.1.1.1
# 在线工具检查全球 DNS 传播
# https://dnschecker.org/
步骤 4:修复 webroot 或 standalone 模式问题
如果使用 webroot 模式但路径配置错误,Let's Encrypt 将无法访问验证文件。检查续期配置文件中的 webroot 路径是否与实际的 Nginx/Apache 文档根目录一致:
# 查看续期配置文件
sudo cat /etc/letsencrypt/renewal/example.com.conf
# 关键配置项:
# authenticator = webroot
# webroot_path = /var/www/html,
# [[webroot_map]]
# example.com = /var/www/html
# 确认 Nginx 文档根目录
grep -r "root " /etc/nginx/sites-enabled/ | grep example
# 验证验证文件是否可访问
echo "test" > /var/www/html/.well-known/acme-challenge/test
curl -I http://example.com/.well-known/acme-challenge/test
rm /var/www/html/.well-known/acme-challenge/test
如果 Nginx 配置中有强制 HTTPS 重定向,确保 .well-known/acme-challenge 路径不被重定向:
# 在 Nginx server 块中放行 ACME 验证路径
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
# 不重定向,直接返回文件
default_type "text/plain";
}
# 强制 HTTPS 重定向放在后面
location / {
return 301 https://$host$request_uri;
}
修改 Nginx 配置后重载,然后重新尝试续期:
sudo nginx -t && sudo nginx -s reload
sudo certbot renew --dry-run
步骤 5:设置自动续期和监控
修复续期问题后,确保自动续期机制正常运行,并设置监控以便在续期失败时及时收到通知:
# 检查 certbot 定时器状态
sudo systemctl list-timers | grep certbot
# 手动测试自动续期定时器
sudo systemctl start certbot.timer
sudo systemctl status certbot.timer
# 查看 certbot 续期脚本
cat /etc/cron.d/certbot
# 或
systemctl cat certbot.timer
# 创建监控脚本检查证书到期时间
cat > /usr/local/bin/check-cert-expiry.sh << 'SCRIPT'
#!/bin/bash
DAYS=30
certs=$(find /etc/letsencrypt/live -name "cert.pem" 2>/dev/null)
for cert in $certs; do
expiry=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ $days_left -lt $DAYS ]; then
echo "WARNING: $cert expires in $days_left days"
# 此处可添加邮件通知逻辑
fi
done
SCRIPT
sudo chmod +x /usr/local/bin/check-cert-expiry.sh
# 添加到 crontab 每日检查
echo "0 8 * * * /usr/local/bin/check-cert-expiry.sh" | sudo crontab -
建议同时配置续期后自动重载 Web 服务器,确保证书更新后立即生效:
# 在 /etc/letsencrypt/renewal-hooks/deploy/ 目录下创建部署钩子
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'SCRIPT'
#!/bin/bash
nginx -t && nginx -s reload
SCRIPT
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
快速参考:原因与修复
| 症状 / 日志信息 | 根本原因 | 修复方法 |
|---|---|---|
Problem binding to port 80 |
standalone 模式下 Web 服务器占用 80 端口 | 切换为 webroot 模式或续期时临时停止 Web 服务 |
DNS problem: NXDOMAIN |
DNS A 记录不存在或指向错误 IP | 更新 DNS 记录并等待传播完成 |
Connection refused 验证失败 |
防火墙拦截了 80 端口入站流量 | 放行 80 端口,确保 Let's Encrypt 可访问 |
| webroot 验证返回 404 | webroot 路径与实际文档根目录不匹配 | 更新续期配置中的 webroot_path 路径 |
| HTTPS 重定向导致验证失败 | Nginx 强制重定向拦截了 ACME 验证路径 | 为 .well-known/acme-challenge 路径添加放行规则 |
| 续期成功但浏览器仍显示过期 | 续期后未重载 Web 服务器 | 配置 renewal-hooks 部署钩子自动重载 |
| Cloudflare 用户续期失败 | Cloudflare 代理干扰了 HTTP-01 验证 | 暂停 Cloudflare 代理或改用 DNS-01 验证 |