# Certbot + Cloudflare DNS 插件申请通配符证书

> 用 Certbot 的 certbot-dns-cloudflare 插件申请 Let's Encrypt 通配符证书：受限 API Token、凭据文件、DNS-01 签发，以及自动续期和重载。

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

Certbot 申请通配符证书要用 certbot-dns-cloudflare 插件：插件用 Cloudflare API Token 添加 `_acme-challenge` TXT 记录完成 DNS-01 验证，为 `example.com` 和 `*.example.com` 签发 Let's Encrypt 证书，续期和重载都自动完成。

适用于用 snap（或 pip）安装 Certbot 4.1 及以上、DNS 托管在 Cloudflare 的 Linux 服务器。

## 前提条件

DNS-01 不需要开放入站端口，80 端口不通、未备案的服务器也能签发，有该域名的 Cloudflare API Token 就够了。

- 带 systemd 和 snapd 的 Linux 服务器，有 sudo 权限。
- 域名的 NS 已经切到 Cloudflare（控制台显示 **Active**）。
- 在 **My Profile → API Tokens** 创建 Token，权限选 Zone → DNS → Edit，范围只选 `example.com`。
- 服务器能访问 Let's Encrypt 和 `api.cloudflare.com`。

## 第一步：安装 Certbot 和 Cloudflare 插件

snap 版 Certbot 要先允许插件以 root 运行，再安装 `certbot-dns-cloudflare`：

```bash
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare
certbot plugins | grep -A1 dns-cloudflare
```

用 pip 装在 `/opt/certbot` 的，改为运行 `sudo /opt/certbot/bin/pip install certbot-dns-cloudflare`。

## 第二步：把 Token 写进凭据文件

插件从凭据文件读取 Token，这个文件只能让 root 读取：

```bash
sudo mkdir -p /root/.secrets/certbot
sudo tee /root/.secrets/certbot/cloudflare.ini > /dev/null <<'EOF'
dns_cloudflare_api_token = your-cloudflare-api-token
EOF
sudo chmod 600 /root/.secrets/certbot/cloudflare.ini
```

文件能被其他用户读取时 Certbot 会报警。

## 第三步：签发通配符证书

`certonly` 只申请证书、不改 Web 服务器配置，主域名和通配符写在同一条命令里：

```bash
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
  -d example.com -d '*.example.com'
```

`'*.example.com'` 要加引号防止被 Shell 展开。必须同时写 `example.com`，通配符不包含主域名。证书保存在 `/etc/letsencrypt/live/example.com/`。

## 第四步：使用证书并添加重载钩子

Web 服务器引用 `fullchain.pem` 和 `privkey.pem`，以 Nginx 为例：

```nginx
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
```

`certonly` 不会改服务器配置，需要加部署钩子让续期后自动重载：

```bash
sudo certbot reconfigure --cert-name example.com --deploy-hook "systemctl reload nginx"
```

也可以在签发命令里加 `-i nginx`，让 Nginx 安装器自动修改配置并重载。

## 验证

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

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

在服务器上：

```bash
sudo certbot certificates
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -ext subjectAltName
```

## 自动续期

插件名、凭据路径和等待时间都保存在 `/etc/letsencrypt/renewal/example.com.conf`，snap 定时器每天两次按需续期，Certbot 4.1 及以上会按 ARI 建议时间续期。测试：

```bash
sudo certbot renew --dry-run
systemctl list-timers | grep certbot
```

Token 失效会导致续期失败：轮换或删除 Token 后要同步更新凭据文件。Let's Encrypt 不再发到期提醒邮件，建议把域名加到 [ssl.sh 监控](https://ssl.sh/zh/monitor/)（开发中）。钩子的更多用法见 [Certbot 续期钩子](https://ssl.sh/zh/guides/certbot-renewal-hooks/)。

## 常见问题排查

- **Error determining zone_id 或 9109: Invalid access token**：Token 错误、过期或没有这个域名的权限，重新创建 Zone → DNS → Edit 的 Token。
- **Unsafe permissions on credentials configuration file**：对 ini 文件执行 `sudo chmod 600`。
- **DNS problem: NXDOMAIN looking up TXT**：记录还没生效，加 `--dns-cloudflare-propagation-seconds 60`。
- **CAA record prevents issuance**：CAA 记录没有 `letsencrypt.org`，或 `issuewild` 排除了它，见 [CAA 记录阻止签发](https://ssl.sh/zh/errors/caa-record-prevents-issuance/)。
- **too many certificates already issued**：测试时用 `--dry-run` 或 `--test-cert`，见 [Let's Encrypt 频率限制](https://ssl.sh/zh/errors/lets-encrypt-rate-limit/)。
- **二级子域名提示证书不匹配**：`a.b.example.com` 不在 `*.example.com` 范围内，见 [NET::ERR_CERT_COMMON_NAME_INVALID](https://ssl.sh/zh/errors/net-err-cert-common-name-invalid/)。
