acme.sh 说续期成功了,证书却还是旧的
acme.sh 的定时任务显示成功,浏览器里的证书却没变。本文按「有没有续下来」和「续下来了但没生效」两条岔路,逐一排查路径不匹配、reloadcmd 未执行、宝塔证书目录、版本过旧等成因,每一步都给出确认命令。
acme.sh 的定时任务跑完没报错,日志里写着 Cert success,浏览器里看到的却还是那张旧证书 —— 甚至已经过期了。
这句「成功」和你看到的现象不矛盾,因为它们说的不是同一件事。acme.sh 的成功只到「新证书拿到手并写进文件」为止,把它交给正在跑的服务是另一件事,而那一步失败时通常是静默的。
所以排查的第一步不是看日志,是先判断自己在哪条岔路上。
先分岔:是没续下来,还是续下来了没生效
# ① acme.sh 自己那份,什么时候签的
acme.sh --info -d example.com | grep -E "Le_CertCreateTimeStr|Le_NextRenewTimeStr"
# ② 服务器实际发给访客的那份
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates| ① 和 ② 的关系 | 你在这条岔路 |
|---|---|
| ① 是新的,② 是旧的 | 续下来了,但没生效 → 第 1、2、3 节 |
| ① 也是旧的 | 根本没续下来 → 第 4、5 节 |
--info 什么都没输出 | 域名不在这个 acme.sh 账户下,或者你查的用户不对 → 第 4 节第一段 |
别用浏览器判断 ②。浏览器会缓存连接和中间证书,你刷新看到的可能不是访客看到的。上面那条 openssl 命令问的是服务器当下真正发出的东西。
1. reloadcmd 没配,或者配了但没执行
这是最常见的一种。--install-cert 时如果没给 --reloadcmd,acme.sh 会把新证书写到目标路径,然后什么也不做 —— nginx 仍然握着启动时读进内存的那份旧证书。
acme.sh --info -d example.com | grep Le_ReloadCmd空的就是没配。补上:
acme.sh --install-cert -d example.com \
--key-file /path/to/key.pem \
--fullchain-file /path/to/fullchain.pem \
--reloadcmd "nginx -s reload"--install-cert 是幂等的,重复执行只是更新配置,不会重新签发。
2. 宝塔面板的 nginx 不吃 force-reload
很多教程里的 reloadcmd 写的是 service nginx force-reload。宝塔自带的 nginx 不支持这个动作,命令直接失败,而 acme.sh 不会因为 reloadcmd 失败就把整次续期判为失败。
宝塔环境下改成:
--reloadcmd "/etc/init.d/nginx reload"改完之后手动跑一次 reloadcmd 本身,确认它真的返回 0:
/etc/init.d/nginx reload; echo "退出码 $?"3. 宝塔把证书存在自己的目录里
宝塔管理的站点,证书读的是它自己的路径:
/www/server/panel/vhost/cert/example.com/fullchain.pem
/www/server/panel/vhost/cert/example.com/privkey.pem而 acme.sh 默认把证书放在 ~/.acme.sh/example.com_ecc/(ECC 证书带 _ecc 后缀)。两边是两份文件,acme.sh 更新了自己那份,宝塔那份纹丝不动。
不要手工复制,那只能解决这一次。让 --install-cert 直接写进宝塔的路径:
acme.sh --install-cert -d example.com --ecc \
--key-file /www/server/panel/vhost/cert/example.com/privkey.pem \
--fullchain-file /www/server/panel/vhost/cert/example.com/fullchain.pem \
--reloadcmd "/etc/init.d/nginx reload"--ecc 不能省,如果你的证书是 ECC 的。acme.sh 用它区分同一域名下的 RSA 与 ECC 两套目录,漏掉时会提示找不到证书,或者安装的是另一套。
4. 定时任务里的路径和实际安装位置不一致
acme.sh 的定时任务长这样:
0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null如果安装时用的是别的用户、或者中途换过 --home,crontab 里这个路径就会指向一个空目录 —— 任务照常运行、照常退出 0、什么也没做。而 > /dev/null 把唯一能看出问题的输出也丢掉了。
# crontab 里写的是哪个路径
crontab -l | grep acme
# 证书实际在哪
ls -d ~/.acme.sh/*/ 2>/dev/null两者不一致就改 crontab。顺便把 > /dev/null 换成一个日志文件,下次就不用猜了:
0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" >> /var/log/acme.log 2>&1用 root 装的 acme.sh,定时任务也必须在 root 的 crontab 里。sudo crontab -l 和 crontab -l 看到的是两份不同的表,这是「我明明配了定时任务」最常见的误会来源。
5. acme.sh 版本太旧
acme.sh 装上之后不会自己升级,除非开了自动升级。旧版本会在 CA 那边的接口调整后突然失效,而且报错通常发生在网络层,看起来像是网络问题。
acme.sh --version
acme.sh --upgrade
acme.sh --upgrade --auto-upgrade # 以后自动升级升级不会动已有证书和配置。
确认真的修好了
不要等下一次自动续期。强制跑一次完整流程:
acme.sh --renew -d example.com --ecc --force然后回到本文开头那条 openssl s_client 命令,确认服务器发出的日期变了。只有这一条能证明修好了 —— acme.sh 自己的日志只能证明前半段。
如果你不想再维护这条链路
上面五种成因有一个共同点:它们都不是 acme.sh 的 bug,而是这条链路上环节太多。签发、写文件、重载服务、定时任务、用户与路径,任何一环安静地断掉,结果都是「日志说成功,证书是旧的」,而你只会在证书过期那天知道。
它还依赖一件事:那台跑 cron 的机器要一直活着、一直被维护。
本站的证书申请把这条链路缩短了一半 —— 验证环节用一条 CNAME 委派后不再需要人工,签发与续期由我们完成。部署那一步仍然在你这边,这一点我们不打算含糊其辞:证书还是要装到你的服务器上。
真正的区别是,需要一直活着的那台机器不再是你的。
另外,无论你用哪种方式续期,都建议把域名加入证书监控:它检查的是访客实际拿到的那张证书,也就是本文开头的 ②。这正是所有「续期脚本说成功了」的故障唯一逃不过的那一关。