证书半夜过期,我用 acme.sh 的 DNS-01 把续期交给了 cron
去年有天晚上十点多,家里群里有人问"你的网盘怎么打不开了"。我打开浏览器一看,地址栏挂着红色警告——证书过期了。签的是 Let's Encrypt 三个月的免费证书,我一直以为它会自己续,结果那次续期因为验证方式的问题没跑成功,我自己也没收到任何提示。
那之后我把整个流程换成了 acme.sh + DNS-01,到现在快一年没管过。写下过程,免得下次又从零开始查。
为什么非得是 DNS-01
我最早用的是 HTTP-01:在网站根目录放一个 .well-known/acme-challenge/ 下的文件,让 Let's Encrypt 来验。这在单个域名、80 端口对外开放的时候挺好用。但我的场景有两个问题。
一是服务都在内网,靠 nginx 反代,80 端口只留了跳转,验证路径经常被重定向规则拦掉,续期就失败了。二是我给 *.home.example.com 底下挂了七八个子服务,一个个签太麻烦,想用泛域名证书——泛域名只能用 DNS-01 验证,HTTP-01 根本不支持。
DNS-01 的原理是:acme.sh 调 DNS 服务商的 API 加一条 _acme-challenge 的 TXT 记录,Let's Encrypt 查到这条记录就算你拥有这个域名。不需要任何端口对外开放,内网机器也能签。
安装和第一次签发
curl https://get.acme.sh | sh -s email=me@example.com
source ~/.bashrc
装完注意一件事:acme.sh 从某个版本开始默认 CA 换成了 ZeroSSL,如果你就想用 Let's Encrypt,手动切一下:
acme.sh --set-default-ca --server letsencrypt
我用的是 DNSPod(腾讯云的),在控制台申请一个 API 密钥,拿到 ID 和 Token,写进 ~/.acme.sh/account.conf,或者直接在当前 shell 里 export:
export DP_Id="123456"
export DP_Key="xxxxxxxxxxxxxxxxxxxxxxxx"
然后签发。我选了 ECC 证书,256 位,体积小一点:
acme.sh --issue --dns dns_dp -d example.com -d '*.example.com' --keylength ec-256
这里有个我踩过的坑:*.example.com 不包含 example.com 本身。 我第一次只写了泛域名,签完部署上去,访问根域名还是报证书不匹配。必须两个 -d 都写上,acme.sh 会生成一张同时覆盖根域和所有子域的 SAN 证书。
另一个坑是 DNS 生效时间。acme.sh 加完 TXT 记录默认等 120 秒再让 CA 来查,大部分情况够用。我遇到过一次解析延迟比较久,查 TXT 老是查不到,加个 --dnssleep 300 就过去了。别急着怀疑密钥写错了,先用 dig _acme-challenge.example.com TXT 看看记录出来没有。
装证书,别直接指过去
签完的证书躺在 ~/.acme.sh/example.com_ecc/ 下面,目录结构是 acme.sh 自己定的,而且续期后里面的文件会被覆盖。官方文档明确说别直接把这个目录配进 nginx。用 --install-cert 拷到固定位置:
acme.sh --install-cert -d example.com --ecc \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com.crt \
--reloadcmd "systemctl reload nginx"
--reloadcmd 是关键。证书文件换了但 nginx 不 reload,进程里还是老证书,浏览器照样报错。我一般用 systemctl reload nginx,比 nginx -s reload 稳一点。
还有个 cron 环境下的老问题:cron 执行时 PATH 特别干净,如果你在 reloadcmd 里写了不带全路径的命令,很可能执行失败但日志里看不出所以然。保险起见写成 "/usr/sbin/nginx -s reload" 这种绝对路径。
续期到底谁来触发
acme.sh 安装时会自动往 crontab 里塞一条:
0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
它每天跑,但只在证书剩余有效期不足 30 天时才真正续期,其余时间直接跳过。所以别看到今天没续就以为坏了。想验证整条链路通不通,手动加 --force 跑一次:
acme.sh --cron --force
跑完用这个看新证书的日期:
openssl x509 -in /etc/nginx/ssl/example.com.crt -noout -dates
或者直接连一下:
echo | openssl s_client -connect home.example.com:443 2>/dev/null | openssl x509 -noout -dates
建议在 --reloadcmd 里把续期结果也推一条到你的通知渠道(我用的是企业微信机器人,curl 一下就完事),这样万一真的续失败了,至少能第一时间知道,而不是等别人来问你怎么打不开了。
剩下的小事
--issue 那次用的 DP_Id/DP_Key 会被 acme.sh 存进 account.conf,之后 cron 续期不需要再 export,这也是为什么那两个密钥只需要在第一次签发时准备。
acme.sh 默认开着自动升级。如果哪天突然报错说某个参数不认识,先 acme.sh --upgrade 一下,很可能就是版本太老。
现在我的 crontab 里就那一条,一年到头不用管。证书的活儿应该就是这样——签完一次就忘掉它。
- 点赞
- 收藏
- 关注作者
评论(0)