侧边栏壁纸
  • 累计撰写 125 篇文章
  • 累计收到 2 条评论

Certbot 续签失败排查:我踩过的几个 SSL 证书自动续期坑

2026-6-27 / 0 评论 / 10 阅读
🤖AI摘要
MQTT是物联网项目中轻量级、发布/订阅模式的消息协议,适用于设备实时数据上报。本文介绍了选择MQTT的原因,如何搭建Mosquitto服务器,并分享了搭建过程中的经验与常见问题,如匿名访问的安全风险、QoS选择、Topic设计等。最后,通过实战案例展示了如何实现传感器数据上报。

Certbot 续签失败排查:我踩过的几个 SSL 证书自动续期坑

上个月服务器迁移,旧机器上的证书到期了才想起来新机器上 cron 没配。等发现的时候网站已经跳不安全提示两天了。

后来花了几天把 certbot 的自动续期理顺,过程中踩了几个比较隐蔽的坑,记下来。

先搞明白 certbot renew 到底在干嘛

很多人以为 certbot 是个"设好就不管"的东西。实际上它干的事很简单:定时跑 certbot renew,检查证书还有三十天不到期的就重新签。就这么一件事。

问题出在执行环境上。

我第一次配的时候直接在 cron 里写了一行:

0 3 * * * certbot renew --quiet

看着没问题,第二天一看日志,certbot 命令找不到。环境变量没加载,cron 跑的 shell 和用户交互时用的 shell 根本不是一套东西。PATH 里根本没有 certbot 的安装位置。

后来改成绝对路径:

0 3 * * * /usr/bin/certbot renew --quiet

这步解决了"命令找不到"的问题,但还有更隐蔽的坑在后面。

坑一:Nginx 没重启,证书续了但网站还是旧的

certbot renew 默认只下载新证书,不会自动重载 Nginx。也就是说证书文件确实更新了,但 Nginx 还握着旧的文件描述符,用户看到的还是即将过期的那张。

解决办法有两个。一个是加 --deploy-hook

0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"

另一个是在 certbot 的 Nginx 插件模式下,它会自动处理 reload。但前提是你是用 certbot --nginx 申请的证书,不是手动 copy 那种。

我一开始用的是手动 copy 的方式——把证书文件复制到指定目录,Nginx 配置里指向那个路径。这种方式 certbot 不知道你的文件在哪,自然也不会帮你 reload。后来干脆改用 certbot --nginx 插件模式,让 certbot 直接改 Nginx 配置,省心不少。

坑二:Webroot 模式和 Standalone 模式选错了

certbot 验证域名所有权有两种常见方式:webroot 和 standalone。

webroot 模式要求你的服务器上已经有一个能正常访问的 Nginx,certbot 会在 .well-known/acme-challenge/ 目录下放一个验证文件,Let's Encrypt 的服务器来请求这个文件确认你拥有这个域名。

standalone 模式则是 certbot 自己起一个临时 HTTP 服务器(默认 80 端口)来接收验证。

问题在于 standalone 需要独占 80 端口。如果你的 Nginx 已经在跑 80 端口了,standalone 起不来,续签就失败。

我第一次在 VPS 上测试的时候,Nginx 没开,用的 standalone,一切顺利。后来上了生产环境,Nginx 开着,certbot 还是按 standalone 的方式配的,续签直接报错:

Could not bind TCP port 80

改成 webroot 模式就好了:

certbot certonly --webroot -w /var/www/html -d example.com

webroot 模式下,certbot 往 Nginx 已有的站点根目录里放验证文件,不抢端口,不影响现有服务。

还有一个细节:Nginx 配置里得放行 .well-known 路径。如果你之前为了安全把所有隐藏目录都 deny 了,certbot 的验证请求会被 403 挡在外面,证书照样续不上。

我在 Nginx 配置里加了这么一段:

location ~ /\.well-known {
    allow all;
}

别嫌短,这一段救过我两次续签失败。

坑三:多域名证书里漏了一个

certbot 支持一次性申请包含多个域名的证书(SAN 证书)。我一开始图省事,把主域名和 www 子域名一起申请:

certbot --nginx -d example.com -d www.example.com

后来加了个 API 域名 api.example.com,想着反正都在一台机器上,顺手加进去得了。结果续签的时候发现 certbot 只认最初申请时的那两个域名,新加的不在证书里。

这时候如果只续签,新域名拿不到有效证书。解决办法是重新生成一个包含所有域名的新证书:

certbot --nginx -d example.com -d www.example.com -d api.example.com

重新生成后会替换旧证书。关键是 cron 里的配置也得跟着改——如果 cron 跑的是固定参数的 renew,它可能不会自动包含新域名。

后来我干脆把域名列表单独写到配置文件里,cron 只跑 certbot renew,具体的域名参数放在 /etc/letsencrypt/cli.ini 里:

domains = example.com, www.example.com, api.example.com

这样加域名的时候只需要改 ini 文件,不用动 cron。

坑四:DNS-01 验证的自动化问题

对于某些场景,HTTP 验证(webroot/standalone)不够用。比如你的服务不在 80/443 端口,或者你想申请通配符证书 *.example.com。这时候得用 DNS-01 验证——在域名 DNS 里加一条 TXT 记录证明自己拥有这个域名。

问题在于 DNS-01 验证需要修改 DNS 记录,而大多数 DNS 服务商不提供命令行直接修改的接口。你得用 API,而且得提前拿到 API Token。

我用的是 Cloudflare。配起来也不难,装一个 cloudflare-dns 插件,把 API Token 写在配置文件里:

certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini -d "*.example.com" -d example.com

但这里有个坑:API Token 的权限范围。Cloudflare 的 Token 可以限制只读、只写特定 zone,或者完全访问。我第一次随便生成了一个 Token,结果 certbot 提示权限不足,改不了 DNS 记录。

正确的做法是创建一个只针对目标 Zone 有 DNS 编辑权限的 Token,别给多余的权限。安全起见,权限最小化总是对的。

另外,通配符证书的有效期也是 90 天,和普通证书一样。很多人以为通配符证书能管一年,其实 Let's Encrypt 的政策对所有证书都是 90 天。

坑五:certbot 升级导致插件不兼容

这个坑比较玄学。有一段时间 certbot 自动升级到了新版本,而我用的 Nginx 插件版本还是旧的。升级后跑 certbot renew,提示插件版本不匹配,续签直接失败。

日志里能看到类似这样的报错:

The nginx plugin is not working; there may be problems with your existing configuration.
Error: nginx version mismatch

解决办法是定期更新 certbot 本身:

apt update && apt install --only-upgrade certbot

或者更稳妥的做法,把 certbot 的更新也写进 cron,和续签分开:

0 0 1 * * /usr/bin/apt update && /usr/bin/apt install --only-upgrade certbot -y
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"

每月一号更新 certbot,每天凌晨三点续签。这样基本不会碰到版本不兼容的问题。

不过要注意,apt 更新可能会引入新的依赖或者改变行为。更新之后最好跑一次 dry-run 验证一下:

certbot renew --dry-run

dry-run 不会真的续签,但会模拟整个过程,告诉你哪里有问题。这个命令值得加到 cron 里每周跑一次,比等到证书快过期了才发现续不上要好得多。

我现在的配置

把上面这些坑踩完之后,我现在用的配置大概是这样的:

cron 任务:

# 每月更新 certbot
0 0 1 * * /usr/bin/apt update && /usr/bin/apt install --only-upgrade certbot -y 2>/dev/null

# 每周试运行续签,检查是否有问题
0 5 * * 0 /usr/bin/certbot renew --dry-run --quiet

# 每天凌晨三点实际续签
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"

Nginx 配置里放行 challenge 路径:

location ~ /\.well-known {
    allow all;
}

certbot 参数放在 ini 文件里:

/etc/letsencrypt/cli.ini

webroot-path = /var/www/html
deploy-hook = systemctl reload nginx
email = admin@example.com
agree-tos = y

这样加域名、改路径都只需要改 ini 文件,不用动 cron。

最后说两句

certbot 本身不难用,难的是把它放到一个稳定的运行环境里。环境变量、Nginx 配置、插件版本、域名变更——任何一个环节出问题都会导致续签失败。

我的经验是:先用 dry-run 验证每一步,然后把所有配置集中到 ini 文件里管理,cron 只负责定时触发。出了问题先看日志,日志里没有就去 /var/log/letsencrypt/ 翻。

证书过期这件事,越早发现越好。别等到用户看到"不安全"的提示了才去查。

评论一下?

OωO
取消