Let's Encrypt 证书自动续期实战:用 acme.sh 实现 DNS 泛域名证书无人值守,附续期失败的六大排查
每个站长都经历过那种提心吊胆的时刻:早上一开网站,浏览器弹出「您的连接不是私密连接」,证书过期了。手动申请证书最麻烦的不是第一次配置,而是三个月后你早就忘了这回事。Let's Encrypt 的证书有效期只有 90 天,这是强制设计,就是为了推动自动化。这篇文章讲清楚我用 acme.sh 搭建的全自动续期方案,包括 HTTP-01 和 DNS-01 两种验证方式怎么选、泛域名证书怎么签、Nginx 怎么配置热重载,以及续期失败时最常见的六个坑怎么排。
为什么选 acme.sh 而不是 certbot?
certbot 是 Let's Encrypt 官方推荐,功能全但依赖多、体积大。acme.sh 是一个纯 Shell 脚本,没有任何外部依赖,几百 KB,支持 100 多家 DNS 服务商的 API 自动验证,最大优势是内置自动续期——装好之后它自己 cron 定时检查,快到期就自动续,续完自动重载 Nginx。对个人站长来说,acme.sh 的「一次配置、永远不用管」体验最好。安装:
curl https://get.acme.sh | sh -s email=you@example.com
# 安装脚本会自动往 crontab 写一条定时任务,并把它自己加到 PATH
source ~/.bashrc安装完会自动创建 ~/.acme.sh/ 目录,并在 crontab 里加入一条每天检查的定时任务。你可以用 crontab -l | grep acme 确认。
HTTP-01 还是 DNS-01?两种验证方式的选择
Let's Encrypt 需要验证你确实拥有这个域名,有几种方式,个人站长主要用两种:
- HTTP-01:CA 来访问
http://你的域名/.well-known/acme-challenge/xxx,你要能返回指定内容。优点是简单,缺点是只能签单域名/多域名,签不了泛域名,而且要求 80 端口对公网开放且可访问。 - DNS-01:你在域名的 DNS 里加一条 TXT 记录证明所有权。优点是支持泛域名(*.example.com),而且不需要 80 端口对外开放(适合只开 443 的站)。缺点是要配置 DNS 服务商的 API 密钥。
我的建议:如果你想要一张证覆盖主域和所有子域,或者服务器在国内、80 端口被运营商封了,直接用 DNS-01。以 Cloudflare 托管 DNS 为例:
# 先设置 DNS API 凭证
export CF_Token="你的Cloudflare API Token"
export CF_Account_ID="你的Account ID"
# 签泛域名 + 主域名(用 dns_cf 验证)
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'注意泛域名要写成 '*.example.com' 加单引号,防止 Shell 把它当通配符展开。API Token 要设置成只对「Zone:DNS:Edit」有权限,遵循最小权限原则,万一泄露损失可控。acme.sh 会自动把这条 TXT 记录加进去、等解析生效、验证、再删掉,全自动。
如果走 HTTP-01,命令是:
acme.sh --issue -d example.com -d www.example.com --webroot /var/www/html--webroot 指定网站根目录,acme.sh 会往里放验证文件。也可以加 --nginx 让 acme.sh 自动临时改 Nginx 配置,但我不推荐——它会动你的配置,出问题不好排查,还是 webroot 干净。
关键一步:用 --install-cert 让证书真正被 Nginx 用上
很多人栽在这里:以为 --issue 完就万事大吉,其实证书只是被 acme.sh 存在自己目录里,你还得「安装」到目标位置,并告诉 acme.sh 续期成功后要执行什么命令重载 Nginx。这一步是自动续期能真正生效的核心:
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com.crt \
--reloadcmd "systemctl reload nginx"这条命令做了三件事:把私钥和完整证书链复制到 Nginx 目录;注册一条钩子,告诉 acme.sh「以后每次续期成功,都重新复制并执行 reloadcmd」。--reloadcmd "systemctl reload nginx" 就是让 Nginx 重新加载证书。必须用 reload 而不是 restart,reload 不中断现有连接,restart 会瞬断。这一步配好,之后就真的不用管了。
Nginx 对应配置:
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_trusted_certificate /etc/nginx/ssl/example.com.crt;
# ... 其他配置
}推荐一个强制 HTTPS 跳转:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}手动测试一次续期,别等三个月后才发现坏了
自动续期最坑的是「静默失败」——你以为它在自动续,其实它一直失败,直到证书过期你才知道。所以配好后一定要手动跑一次续期测试:
# 强制立即续期一次,看是否有报错
acme.sh --renew -d example.com --force --debug--force 忽略「还没到期」的判断,强制走一遍完整流程。--debug 打印详细日志。如果这一步成功,说明整条链路(DNS API、文件权限、reloadcmd)都通了。建议把这条命令加进你的例行检查清单,或者干脆写个脚本每月跑一次。
再看 acme.sh 里的证书信息:
acme.sh --list
# 输出示例:
# Main_Domain KeyLength SAN_Domains CA Created Renew
# example.com ec-256 *.example.com LetsEncrypt 2026-09-01T... 2026-11-30T...确认 Renew 时间合理,且 CA 是 LetsEncrypt。
续期失败的六大常见原因排查
下面是我这些年踩过的坑,按出现频率排序:
- cron 里的环境变量丢失。 acme.sh 的续期是 cron 跑的,但
export CF_Token=...这类变量只在当前 Shell 生效。解决:把凭证写进 acme.sh 的账号配置~/.acme.sh/account.conf(acme.sh 通常会自动保存),或者直接在 acme.sh 目录下建一个~/.acme.sh/dns_cf.sh存密钥。 - DNS API Token 权限或过期。 Token 被误删、权限不含 DNS Edit、或者 IP 白名单限制,都会导致 dns_cf 验证失败。用
--debug看具体报错。 - DNS 解析生效延迟。 添加 TXT 记录后 CA 来查,如果本地 DNS 还没生效会验证失败。可以给 acme.sh 加等待:
--dnssleep 120让它等两分钟再验证。 - Nginx 文件权限。 证书复制到
/etc/nginx/ssl/后,如果 Nginx 的运行用户(www-data)没有读权限,reload 会失败。确保目录和文件权限正确:chmod 644证书、chmod 600私钥,属主设对。 - reloadcmd 里用了相对路径或缺少 PATH。 cron 环境 PATH 很简,
systemctl可能要写全路径/usr/bin/systemctl。reloadcmd 里的命令一定用绝对路径。 - 80 端口被占用 / HTTP-01 验证文件路径不对。 走 webroot 时,Nginx 必须能把
/.well-known/acme-challenge/指到 webroot 目录,若站点有跳转或防盗链规则拦了它就会失败。检查/var/log/nginx/error.log。
监控:给证书过期上最后一道保险
自动化不是万无一失,万一哪天 API 变了、cron 没跑,你还是要靠监控兜底。最朴素的方案:写个脚本每天检查证书剩余天数,少于 15 天就发邮件告警:
#!/bin/bash
DOMAIN="example.com"
END=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
END_TS=$(date -d "$END" +%s)
NOW_TS=$(date +%s)
DAYS=$(( (END_TS - NOW_TS) / 86400 ))
if [ "$DAYS" -lt 15 ]; then
echo "证书 $DOMAIN 还有 $DAYS 天过期!" | mail -s "[告警] SSL 证书即将过期" you@example.com
fi加进 crontab 每天跑一次。这样即使 acme.sh 静默失败,你也会在过期前两周收到提醒,留足排查时间。
总结:用 acme.sh 做证书自动续期,核心就三步——用 DNS-01 签泛域名证书、用 --install-cert 注册 reloadcmd 钩子、手动 --force 测一次。避开 cron 环境变量、API 权限、文件权限这几个高频坑,再配一个剩余天数监控兜底,证书这件事你就能彻底从待办清单里划掉。对个人站长来说,把重复的运维动作自动化,省下的时间精力才是最值钱的。