ACME 客户端与自动化
acme.sh 通过 DNS API 申请通配符 SSL 证书
acme.sh 通过 DNS 服务商的 API 自动添加 _acme-challenge TXT 记录完成 DNS-01 验证,就能为 example.com 和 *.example.com 签发 Let's Encrypt 通配符证书。签发后用 --install-cert 把证书装到固定路径,之后 acme.sh 自动续期并重载服务。
示例用 Cloudflare DNS,其他服务商只是 --dns 参数和变量名不同;国内阿里云 DNS、DNSPod 的完整写法见 acme.sh + 阿里云 DNS/DNSPod。适用于 acme.sh 3.1.4 及以上,Linux、macOS、BSD 均可。
前提条件
DNS-01 不需要开放任何端口,能通过 API 修改域名解析就够了。80 端口被封、域名未备案的服务器也能用。
- 类 Unix 系统,有
curl或wget、openssl、cron,用 root 或者能写证书目录、能重载 Web 服务的用户。 - 域名的权威 DNS 在 acme.sh 支持的服务商(列表见 dnsapi wiki)。
- 只能修改该域名解析记录的 API 密钥。Cloudflare 创建 API Token,权限选 Zone → DNS → Edit,范围限定
example.com,并记下域名概览页的 Zone ID。 - 服务器能访问 CA 和 DNS 服务商的 API。
第一步:安装 acme.sh
官方脚本会把 acme.sh 装到 ~/.acme.sh/,添加 acme.sh 别名并创建 cron 任务。用 Let's Encrypt 时邮箱可以不填。
curl https://get.acme.sh | sh -s email=admin@example.com
exec $SHELL -l
acme.sh --version
国内服务器访问 GitHub 慢时,官方 wiki 提供了从 Gitee 安装的方法:
git clone https://gitee.com/neilpang/acme.sh.git
cd acme.sh
./acme.sh --install -m admin@example.com
第二步:把默认 CA 改成 Let's Encrypt
acme.sh 的默认 CA 是 ZeroSSL,要从 Let's Encrypt 签发,先切换默认 CA:
acme.sh --set-default-ca --server letsencrypt
也可以不改默认值,在每条命令里单独加 --server letsencrypt。
第三步:导出 DNS API 密钥
acme.sh 从环境变量读取 DNS API 密钥。Cloudflare 的受限 Token 用 CF_Token 和 CF_Zone_ID 两个变量:
export CF_Token="your-cloudflare-api-token"
export CF_Zone_ID="your-zone-id"
一个 Token 管理同一账户下多个域名时,把 CF_Zone_ID 换成 CF_Account_ID。其他服务商的 --dns dns_xxx 名称和变量名到 acme.sh 的 dnsapi wiki 查。
第四步:签发通配符证书
一条 --issue 命令同时写主域名和通配符,证书就能覆盖两者:
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'
acme.sh 通过 API 添加 _acme-challenge TXT 记录,等待生效后让 CA 验证,完成后自动删除记录。'*.example.com' 要加引号,防止被 Shell 展开。想用 Let's Encrypt 的 45 天证书,加 --cert-profile tlsserver,见 Let's Encrypt 证书配置文件。
第五步:安装证书并设置重载命令
不要让 Web 服务器直接引用 ~/.acme.sh/ 里的文件,目录结构以后可能变化。用 --install-cert 复制到固定路径:
mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d example.com --ecc \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com.fullchain.pem \
--reloadcmd "systemctl reload nginx"
--ecc 表示使用 ECDSA 证书(acme.sh 默认密钥类型)。然后在 Nginx 里引用:
ssl_certificate /etc/nginx/ssl/example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
验证
签发成功后,主域名和任意子域名都返回同一张 Let's Encrypt 证书。用 SSL 证书检查 检查任意子域名,或在终端运行:
curl ssl.sh/example.com
curl ssl.sh/www.example.com
本地查看 acme.sh 管理的证书和证书里的域名:
acme.sh --list
openssl x509 -in /etc/nginx/ssl/example.com.fullchain.pem -noout -ext subjectAltName -enddate
自动续期
安装时创建的 cron 任务会运行 acme.sh --cron;3.1.4 起每 6 小时运行一次,并默认按 CA 的 ARI 续期窗口续期(设置 NO_ARI=1 可关闭)。没有 ARI 时按 acme.sh 自己的周期续期,可以用 --days 覆盖;3.1.5 起显式指定的 --days 优先于 ARI。每次续期后会重新执行安装步骤和 --reloadcmd。
crontab -l | grep acme.sh
acme.sh --cron --home ~/.acme.sh
acme.sh --upgrade --auto-upgrade
测试完整续期流程请用测试环境(--staging),不要在生产环境反复 --force,会消耗频率限制。Let's Encrypt 不再发提醒邮件,建议把域名加到 ssl.sh 监控(开发中)。
常见问题排查
- Error add txt 或 invalid token:Token 没有这个域名的解析编辑权限,或变量名写错,重新导出后再运行。
- Verify error: no TXT record found:TXT 记录还没同步到所有权威服务器,加
--dnssleep 120延长等待时间。 - CAA record prevents issuance:CAA 记录要允许
letsencrypt.org(用通配符时还要看issuewild),见 CAA 记录阻止签发。 - 触发频率限制:测试签发太多次,用
--staging调试,见 Let's Encrypt 频率限制。 - 续期后浏览器还是旧证书:Web 服务器引用了
~/.acme.sh/里的文件,或重载命令执行失败,查看~/.acme.sh/acme.sh.log。 - 提示缺少中间证书:安装时用了
--cert-file而不是--fullchain-file,见 缺少中间证书。
常见问题
通配符证书包含主域名吗?
不包含。*.example.com 能匹配 www.example.com、api.example.com,但不匹配 example.com 本身,也不匹配 a.b.example.com。申请时同时写 -d example.com -d '*.example.com'。
DNS API 密钥保存在哪里?
第一次签发成功后,acme.sh 会把密钥以 SAVED_... 的形式写进 ~/.acme.sh/account.conf,续期时自动读取。这个文件要保护好,密钥只授予对应域名的解析修改权限,服务器被入侵后要立即更换。
域名在阿里云 DNS 或 DNSPod,怎么写?
把 --dns dns_cf 换成 dns_ali(阿里云)或 dns_tencent(腾讯云 DNSPod),并导出对应的密钥变量。RAM 子账号权限、变量名和常见报错见 acme.sh + 阿里云 DNS/DNSPod。
acme.sh 里的 dns-persist-01 是什么?
acme.sh 3.1.4 增加了 dns-persist-01 验证(--dns-persist-days),用一条绑定到 ACME 账户的长期 TXT 记录完成验证,不用每次签发都改 DNS。CA 是否已在生产环境开放以官方公告为准。