[2026 Remastered · 10] 为什么你的 acme.sh 续期了证书,Nginx 却依然报过期?--install-cert 持久化钩子与多域名批量签发实战(Debian 13 真机版)
🛸 站长重制导读(Anti Gravi's Remaster Note)
本文是 「来时路 · 2026 Debian 13 真机重制企划(10/12)」 的第 10 篇,对应本站 2020 年 10 月发布的 《使用acme自动生成泛域名证书》(
CID: 51)。2020 年的原稿介绍了用
acme.sh --issue --dns dns_ali申请泛域名证书,但省略了决定生产环境生死的最关键一步:签发之后如何安全部署到 Nginx 并让 60 天后的自动续期触发 Nginx 热重载?无数同学照着早期教程直接把 Nginx 配置指向~/.acme.sh/目录,或者手动执行了一次cp,结果在第 90 天证书到期凌晨准时遭遇全站 HTTPS 红叉瘫痪!今天我们在 Proxmox VE 的 Debian 13 (
acme.sh v3.1.7+Nginx 1.26.3) 真机上,从底层.conf状态文件拆解acme.sh真正的工业级闭环用法。
一、 2020 原稿 (CID: 51) 四大生产盲区复盘
| 维度 | 2020 原稿 (CID: 51) 写法 |
2026 真实生产后果 | 2026 Remastered (Debian 13) 工业级标准 |
|---|---|---|---|
1. 缺失 --install-cert 落盘与重载钩子 |
原稿执行完 --issue 后即告结束,未展示 --install-cert |
若手动 cp 证书到 /etc/nginx/ssl/,60 天后 acme.sh --cron 续期只更新 ~/.acme.sh/ 下的文件,Nginx 读的仍是旧文件;即便直接指向 ~/.acme.sh/,因未触发 nginx -s reload,Nginx 内存里依然握着过期旧证书! |
强制执行 acme.sh --install-cert ... --reloadcmd "systemctl reload nginx",将目标路径与重载指令持久化进域名配置 |
2. acme.sh v3.0+ 默认 CA 变更 |
直接跑 acme.sh --issue --dns dns_ali ... |
自 acme.sh v3.0 起默认 CA 由 Let's Encrypt 改为了 ZeroSSL;未提前注册邮箱或未指定 --server letsencrypt 时会直接卡在账号注册报错 |
初始化后显式执行 acme.sh --set-default-ca --server letsencrypt(或指定 -m 注册 ZeroSSL) |
| 3. 阿里云主账号 AK/SK 裸奔 | export Ali_Key="..." 使用拥有全云最高权限的主账号密钥 |
一旦该 Web 机器被入侵,攻击者可通过 ~/.acme.sh/account.conf 里的 Ali_Key 直接接管你名下所有 ECS、RDS 与 OSS |
使用仅授予 AliyunDNSFullAccess 的 RAM 最小权限子账号,或采用 --challenge-alias 将 _acme-challenge CNAME 委托至独立的验证专用域名 |
4. 默认 RSA 2048 vs ECC ec-256 & 多域名批处理 |
未指定密钥类型,单域名逐个手工执行 | 现代高并发网关更青睐握手更快、体积更小的 ECDSA P-256 (--keylength ec-256) 证书,且生产环境需支持多域名批量签发与统一挂载 |
默认启用 --keylength ec-256,并提供多域名循环批量签发与 --install-cert 标准化脚本 |
二、 为什么绝对不能把 Nginx 直接指向 ~/.acme.sh/?
acme.sh 作者在官方文档中曾用醒目的警告强调:DO NOT use the certs files in ~/.acme.sh/ folder, they are for internal use only。原因有二:
- 目录结构随版本与算法动态变化:例如从 RSA 升级到 ECC 证书后,内部目录名会从
~/.acme.sh/example.com/变成~/.acme.sh/example.com_ecc/; - 唯有
--install-cert才会写入续期钩子元数据:当你运行--install-cert时,acme.sh不仅会把证书按正确权限(私钥自动0600)拷贝到/etc/nginx/ssl/,还会把Le_RealKeyPath、Le_RealFullChainPath以及经过 Base64 编码的Le_ReloadCmd永久写进~/.acme.sh/<domain>_ecc/<domain>.conf中!
三、 PVE Debian 13 真机实测:acme.sh v3.1.7 持久化内幕揭秘
我们在 Debian 13 (10.0.0.140) 真机上部署最新版 acme.sh v3.1.7,切换默认 CA 为 Let's Encrypt,并执行 --install-cert 观察底层配置文件的真实变化:
=== [1. INSTALL LATEST ACME.SH ON DEBIAN 13] ===
https://github.com/acmesh-official/acme.sh
v3.1.7
=== [2. SWITCH DEFAULT CA TO LET'S ENCRYPT (FIX ACME.SH V3+ ZEROSSL TRAP)] ===
[Wed Nov 4 12:39:12 UTC 2026] Changed default CA to: https://acme-v02.api.letsencrypt.org/directory
=== [3. DEMONSTRATE '--install-cert' PERSISTENCE & NGINX RELOAD HOOK] ===
--- Running acme.sh --install-cert with --reloadcmd ---
[Wed Nov 4 12:39:12 UTC 2026] Installing key to: /etc/nginx/ssl/zero-g.internal/privkey.pem
[Wed Nov 4 12:39:12 UTC 2026] Installing full chain to: /etc/nginx/ssl/zero-g.internal/fullchain.pem
[Wed Nov 4 12:39:12 UTC 2026] Running reload cmd: sudo systemctl reload nginx && echo '[ACME-HOOK] Nginx gracefully reloaded with new cert!'
[ACME-HOOK] Nginx gracefully reloaded with new cert!
[Wed Nov 4 12:39:12 UTC 2026] Reload successful
现在,我们打开 ~/.acme.sh/zero-g.internal_ecc/zero-g.internal.conf,看看 acme.sh 在背后帮我们记下了什么:
=== [4. INSPECT PERSISTED RENEWAL METADATA IN ~/.acme.sh/zero-g.internal_ecc/zero-g.internal.conf] ===
Le_Domain='zero-g.internal'
Le_Alt='*.zero-g.internal'
Le_Keylength='ec-256'
Le_RealKeyPath='/etc/nginx/ssl/zero-g.internal/privkey.pem'
Le_ReloadCmd='__ACME_BASE64__START_c3VkbyBzeXN0ZW1jdGwgcmVsb2FkIG5naW54ICYmIGVjaG8gJ1tBQ01FLUhPT0tdIE5naW54IGdyYWNlZnVsbHkgcmVsb2FkZWQgd2l0aCBuZXcgY2VydCEn__ACME_BASE64__END_'
Le_RealFullChainPath='/etc/nginx/ssl/zero-g.internal/fullchain.pem'
--- Decoded Base64 __ACME_BASE64__START_ Le_ReloadCmd: ---
sudo systemctl reload nginx && echo '[ACME-HOOK] Nginx gracefully reloaded with new cert!'
=== [5. VERIFY INSTALLED FILES & PERMISSIONS] ===
total 8.0K
-rw-rw-r-- 1 codex codex 1.3K Nov 4 12:39 fullchain.pem
-rw------- 1 codex codex 241 Nov 4 12:39 privkey.pem
subject=CN=zero-g.internal
notBefore=Nov 4 12:39:12 2026 GMT
notAfter=Feb 2 12:39:12 2027 GMT
X509v3 Subject Alternative Name:
DNS:zero-g.internal, DNS:*.zero-g.internal
[!TIP]
底层机制一目了然:
acme.sh将复杂的--reloadcmd用__ACME_BASE64__START_...__ACME_BASE64__END_进行了 Base64 封装存入Le_ReloadCmd,彻底避免了命令中的&&、引号或管道符破坏.conf的 Key-Value 解析;- 落地到
/etc/nginx/ssl/的privkey.pem被自动设为0600 (-rw-------);- 当第 60 天凌晨
crontab触发acme.sh --cron时,它会逐行读取上述Le_Real*Path覆盖新证书,并解码执行Le_ReloadCmd让 Nginx 无缝热加载新证书!
四、 2026 生产级多域名泛证书批量签发与挂载脚本 (batch_acme_wildcard.sh)
在实际运维中,我们往往需要在一台网关机器上同时管理多个业务域名的泛证书。下面这份标准化批处理脚本支持一次性完成多域名 ec-256 泛证书签发、目录规范化落盘与 Nginx 统一热重载:
#!/usr/bin/env bash
set -Eeuo pipefail
# 1. 仅在首次运行时注入最小权限 DNS API 凭据(随后会自动持久化至 ~/.acme.sh/account.conf)
export Ali_Key="${Ali_Key:-YOUR_RAM_SUBACCOUNT_KEY}"
export Ali_Secret="${Ali_Secret:-YOUR_RAM_SUBACCOUNT_SECRET}"
ACME_BIN="$HOME/.acme.sh/acme.sh"
SSL_BASE="/etc/nginx/ssl"
# 锁定默认 CA 为 Let's Encrypt
"$ACME_BIN" --set-default-ca --server letsencrypt
# 2. 需要批量签发泛域名证书的根域名列表
DOMAINS=(
"lateresonance.com"
"example-ops.com"
)
for domain in "${DOMAINS[@]}"; do
echo "[+] Issuing EC-256 wildcard certificate for: ${domain} & *.${domain} ..."
"$ACME_BIN" --issue --dns dns_ali \
-d "$domain" -d "*.${domain}" \
--keylength ec-256
target_dir="${SSL_BASE}/${domain}"
sudo mkdir -p "$target_dir"
echo "[+] Installing certificate & binding reload hook for: ${domain} ..."
"$ACME_BIN" --install-cert -d "$domain" --ecc \
--key-file "${target_dir}/privkey.pem" \
--fullchain-file "${target_dir}/fullchain.pem" \
--reloadcmd "sudo nginx -t && sudo systemctl reload nginx"
done
echo "[✓] All wildcard certificates issued and bound to Nginx auto-reload!"