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 验证

相关指南