# acme.sh 用 DNS API 申请通配符证书（DNS-01）

> 用 acme.sh 通过 DNS 服务商 API 申请 Let's Encrypt 通配符证书：安装、切换默认 CA、DNS-01 签发、安装证书并设置重载命令，附国内安装方法。

Source: https://ssl.sh/zh/guides/acme-sh-dns-api-wildcard/
Last updated: 2026-11-10

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](https://ssl.sh/zh/guides/acme-sh-dnspod-aliyun-dns/)。适用于 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 时邮箱可以不填。

```bash
curl https://get.acme.sh | sh -s email=admin@example.com
exec $SHELL -l
acme.sh --version
```

国内服务器访问 GitHub 慢时，官方 wiki 提供了从 Gitee 安装的方法：

```bash
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：

```bash
acme.sh --set-default-ca --server letsencrypt
```

也可以不改默认值，在每条命令里单独加 `--server letsencrypt`。

## 第三步：导出 DNS API 密钥

acme.sh 从环境变量读取 DNS API 密钥。Cloudflare 的受限 Token 用 `CF_Token` 和 `CF_Zone_ID` 两个变量：

```bash
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` 命令同时写主域名和通配符，证书就能覆盖两者：

```bash
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 证书配置文件](https://ssl.sh/zh/guides/lets-encrypt-certificate-profiles/)。

## 第五步：安装证书并设置重载命令

不要让 Web 服务器直接引用 `~/.acme.sh/` 里的文件，目录结构以后可能变化。用 `--install-cert` 复制到固定路径：

```bash
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 里引用：

```nginx
ssl_certificate     /etc/nginx/ssl/example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
```

## 验证

签发成功后，主域名和任意子域名都返回同一张 Let's Encrypt 证书。用 [SSL 证书检查](https://ssl.sh/zh/tools/ssl-checker/) 检查任意子域名，或在终端运行：

```bash
curl ssl.sh/example.com
curl ssl.sh/www.example.com
```

本地查看 acme.sh 管理的证书和证书里的域名：

```bash
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`。

```bash
crontab -l | grep acme.sh
acme.sh --cron --home ~/.acme.sh
acme.sh --upgrade --auto-upgrade
```

测试完整续期流程请用测试环境（`--staging`），不要在生产环境反复 `--force`，会消耗频率限制。Let's Encrypt 不再发提醒邮件，建议把域名加到 [ssl.sh 监控](https://ssl.sh/zh/monitor/)（开发中）。

## 常见问题排查

- **Error add txt 或 invalid token**：Token 没有这个域名的解析编辑权限，或变量名写错，重新导出后再运行。
- **Verify error: no TXT record found**：TXT 记录还没同步到所有权威服务器，加 `--dnssleep 120` 延长等待时间。
- **CAA record prevents issuance**：CAA 记录要允许 `letsencrypt.org`（用通配符时还要看 `issuewild`），见 [CAA 记录阻止签发](https://ssl.sh/zh/errors/caa-record-prevents-issuance/)。
- **触发频率限制**：测试签发太多次，用 `--staging` 调试，见 [Let's Encrypt 频率限制](https://ssl.sh/zh/errors/lets-encrypt-rate-limit/)。
- **续期后浏览器还是旧证书**：Web 服务器引用了 `~/.acme.sh/` 里的文件，或重载命令执行失败，查看 `~/.acme.sh/acme.sh.log`。
- **提示缺少中间证书**：安装时用了 `--cert-file` 而不是 `--fullchain-file`，见 [缺少中间证书](https://ssl.sh/zh/errors/missing-intermediate-certificate/)。
