强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Certbot 证书自动化教程 / 第 5 章:DNS 验证

第 5 章:DNS 验证

5.1 DNS 验证概述

DNS 验证(DNS-01 Challenge)是通过在域名的 DNS 记录中添加特定的 TXT 记录来证明域名控制权的方式。这是申请通配符证书的唯一方式,也适用于无法开放 80 端口的场景。

为什么需要 DNS 验证

场景HTTP-01DNS-01
通配符证书 *.example.com❌ 不支持✅ 支持
80 端口不可用❌ 需要✅ 不需要
内网服务器❌ 不支持✅ 支持
CDN 后面的服务器❌ 验证困难✅ 不受影响
多层 NAT 环境❌ 验证困难✅ 不受影响

DNS-01 验证流程

1. Certbot 生成验证值
   值: <base64url-encoded-SHA256-of-key-authorization>

2. Certbot(或手动)添加 DNS TXT 记录
   名称: _acme-challenge.example.com
   类型: TXT
   值: <generated-value>

3. Let's Encrypt 查询 DNS
   dig TXT _acme-challenge.example.com

4. 验证记录值匹配 → 域名控制权确认

DNS 插件工作方式

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Certbot    │     │  DNS 插件     │     │  DNS 提供商   │
│              │────>│ (cloudflare/ │────>│ (Cloudflare/  │
│  生成验证值   │     │  route53 等)  │     │  Route53 等)  │
└──────────────┘     └──────────────┘     └──────────────┘
                                                  │
                                                  ▼
                                         添加 _acme-challenge
                                         TXT 记录
                                                  │
                                                  ▼
┌──────────────┐     查询 DNS         ┌──────────────┐
│ Let's Encrypt│ <────────────────────│  DNS 服务器    │
│    服务器     │                      │              │
└──────────────┘                      └──────────────┘

5.2 手动 DNS 验证

不使用 DNS 插件时,可以通过手动方式完成 DNS 验证。

手动申请通配符证书

# 手动 DNS 验证
sudo certbot certonly --manual \
  --preferred-challenges dns \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected]

执行后 Certbot 会提示:

Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:

abcdef1234567890abcdef1234567890abcdef12

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

手动验证步骤

# 1. 登录 DNS 管理面板
# 2. 添加 TXT 记录:
#    名称: _acme-challenge
#    类型: TXT
#    值: abcdef1234567890abcdef1234567890abcdef12
#    TTL: 60

# 3. 等待 DNS 记录生效(通常 1-5 分钟)
dig TXT _acme-challenge.example.com

# 4. 确认记录已生效后,回到终端按 Enter 继续

注意: 如果同时申请 example.com*.example.com,Certbot 会要求添加两条不同的 TXT 记录到 _acme-challenge.example.com

手动验证的局限性

  • 无法自动续期(每次续期都需要手动添加 DNS 记录)
  • 操作繁琐,容易出错
  • 不适合生产环境的自动化管理

5.3 Cloudflare DNS 插件

Cloudflare 是最常用的 DNS 服务提供商之一,Certbot 提供了官方的 Cloudflare DNS 插件。

安装插件

# Snap 安装
sudo snap install certbot-dns-cloudflare

# 或 pip 安装
pip install certbot-dns-cloudflare

获取 Cloudflare API 凭证

方式一:API Token(推荐)

  1. 登录 Cloudflare Dashboard
  2. 进入 My ProfileAPI Tokens
  3. 点击 Create Token
  4. 选择 Edit zone DNS 模板
  5. 配置权限:
    • Zone - DNS - Edit
    • Zone - Zone - Read
  6. 指定区域(Zone)为你的域名
  7. 创建并保存 Token

方式二:Global API Key(不推荐)

  1. 登录 Cloudflare Dashboard
  2. 进入 My ProfileAPI Tokens
  3. 查看 Global API Key

配置凭证文件

# 创建凭证文件
sudo mkdir -p /etc/letsencrypt/cloudflare
sudo tee /etc/letsencrypt/cloudflare/credentials.ini > /dev/null << 'EOF'
# Cloudflare API Token(推荐)
dns_cloudflare_api_token = YOUR_API_TOKEN_HERE
EOF

# 或使用 Global API Key
# dns_cloudflare_email = [email protected]
# dns_cloudflare_api_key = YOUR_GLOBAL_API_KEY

# 设置权限(仅 root 可读)
sudo chmod 600 /etc/letsencrypt/cloudflare/credentials.ini

使用 Cloudflare 插件申请证书

# 申请普通证书
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email [email protected] \
  --non-interactive

# 申请通配符证书
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected] \
  --non-interactive

# 等待 DNS 传播时间(默认 10 秒,可调整)
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  --dns-cloudflare-propagation-seconds 30 \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected] \
  --non-interactive

Cloudflare 插件参数

参数说明默认值
--dns-cloudflare启用 Cloudflare 插件-
--dns-cloudflare-credentials凭证文件路径-
--dns-cloudflare-propagation-secondsDNS 传播等待时间10 秒

5.4 AWS Route53 DNS 插件

安装插件

# pip 安装(Snap 不提供此插件)
pip install certbot-dns-route53

配置 AWS 凭证

# 方式一:环境变量
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"

# 方式二:AWS 凭证文件
mkdir -p ~/.aws
cat > ~/.aws/credentials << EOF
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY
EOF

# 方式三:IAM 角色(EC2 实例推荐)
# 无需配置,自动使用实例角色

所需 IAM 权限

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:GetChange",
        "route53:ChangeResourceRecordSets",
        "route53:ListHostedZonesByName"
      ],
      "Resource": [
        "arn:aws:route53:::hostedzone/*",
        "arn:aws:route53:::change/*"
      ]
    }
  ]
}

使用 Route53 插件申请证书

# 申请通配符证书
sudo certbot certonly \
  --dns-route53 \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected] \
  --non-interactive

Route53 插件参数

参数说明
--dns-route53启用 Route53 插件

注意: Route53 插件不需要额外的凭证文件,使用 AWS SDK 的默认凭证链。

5.5 其他 DNS 插件

DigitalOcean

# 安装
sudo snap install certbot-dns-digitalocean

# 凭证文件
sudo tee /etc/letsencrypt/digitalocean/credentials.ini > /dev/null << 'EOF'
dns_digitalocean_token = YOUR_DIGITALOCEAN_TOKEN
EOF
sudo chmod 600 /etc/letsencrypt/digitalocean/credentials.ini

# 申请证书
sudo certbot certonly \
  --dns-digitalocean \
  --dns-digitalocean-credentials /etc/letsencrypt/digitalocean/credentials.ini \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected]

Google Cloud DNS

# 安装
pip install certbot-dns-google

# 凭证文件(Google Cloud 服务账号 JSON 密钥)
# 申请证书
sudo certbot certonly \
  --dns-google \
  --dns-google-credentials /path/to/credentials.json \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected]

RFC 2136(通用 DNS 更新)

# 安装
pip install certbot-dns-rfc2136

# 凭证文件
sudo tee /etc/letsencrypt/rfc2136/credentials.ini > /dev/null << 'EOF'
dns_rfc2136_server = dns-server-ip
dns_rfc2136_port = 53
dns_rfc2136_name = tsig-key-name
dns_rfc2136_secret = tsig-key-secret
dns_rfc2136_algorithm = HMAC-SHA256
EOF
sudo chmod 600 /etc/letsencrypt/rfc2136/credentials.ini

# 申请证书
sudo certbot certonly \
  --dns-rfc2136 \
  --dns-rfc2136-credentials /etc/letsencrypt/rfc2136/credentials.ini \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected]

Hetzner DNS

# 安装
pip install certbot-dns-hetzner

# 凭证文件
sudo tee /etc/letsencrypt/hetzner/credentials.ini > /dev/null << 'EOF'
dns_hetzner_api_token = YOUR_HETZNER_API_TOKEN
EOF
sudo chmod 600 /etc/letsencrypt/hetzner/credentials.ini

# 申请证书
sudo certbot certonly \
  --dns-hetzner \
  --dns-hetzner-credentials /etc/letsencrypt/hetzner/credentials.ini \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected]

5.6 通配符证书

什么是通配符证书

通配符证书(Wildcard Certificate)可以保护一个域名及其所有同级子域名。例如,*.example.com 可以保护:

  • www.example.com
  • api.example.com
  • mail.example.com
  • blog.example.com

不能保护:

  • example.com(需要单独添加)
  • sub.www.example.com(不能匹配多级子域名)

申请通配符证书

# 同时申请根域名和通配符
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected] \
  --non-interactive

通配符证书文件

# 查看证书信息
sudo certbot certificates --cert-name example.com

# 证书文件位置
/etc/letsencrypt/live/example.com/
├── cert.pem
├── chain.pem
├── fullchain.pem
└── privkey.pem

通配符证书的续期

# 通配符证书续期与普通证书相同
sudo certbot renew --cert-name example.com

# 续期时会自动使用相同的 DNS 插件和凭证
# 需要确保 DNS 凭证文件仍然有效

5.7 DNS 传播时间

DNS 记录的传播需要时间,不同提供商的速度差异较大。

DNS 提供商平均传播时间推荐等待时间
Cloudflare5-10 秒10 秒
AWS Route5330-60 秒60 秒
Google Cloud DNS30-120 秒60 秒
DigitalOcean30-60 秒60 秒
GoDaddy60-600 秒300 秒
传统 DNS 托管300-3600 秒600 秒

设置传播等待时间

# Cloudflare(传播快,10 秒通常足够)
--dns-cloudflare-propagation-seconds 10

# Route53(默认无参数,使用插件默认值)
# 通常 30-60 秒

# RFC 2136
--dns-rfc2136-propagation-seconds 60

验证 DNS 记录

# 查询 TXT 记录
dig TXT _acme-challenge.example.com +short

# 使用 Google Public DNS 查询
dig TXT _acme-challenge.example.com @8.8.8.8 +short

# 使用 Cloudflare DNS 查询
dig TXT _acme-challenge.example.com @1.1.1.1 +short

5.8 DNS 验证自动脚本

自定义 DNS API 脚本

如果 Certbot 没有提供你的 DNS 服务提供商的官方插件,可以使用 --manual-auth-hook--manual-cleanup-hook 实现自动化。

#!/bin/bash
# file: /usr/local/bin/dns-add-record.sh
# 描述:通过 API 添加 DNS TXT 记录

DOMAIN="$CERTBOT_DOMAIN"
VALIDATION="$CERTBOT_VALIDATION"
TOKEN="_acme-challenge"

# 调用 DNS API 添加 TXT 记录
# 示例:使用 curl 调用自定义 API
curl -X POST "https://dns-api.example.com/records" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"type\": \"TXT\",
    \"name\": \"$TOKEN.$DOMAIN\",
    \"value\": \"$VALIDATION\",
    \"ttl\": 60
  }"

# 等待 DNS 传播
sleep 30
#!/bin/bash
# file: /usr/local/bin/dns-remove-record.sh
# 描述:通过 API 删除 DNS TXT 记录

DOMAIN="$CERTBOT_DOMAIN"
TOKEN="_acme-challenge"

# 调用 DNS API 删除 TXT 记录
curl -X DELETE "https://dns-api.example.com/records" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"type\": \"TXT\",
    \"name\": \"$TOKEN.$DOMAIN\"
  }"

使用自定义 Hook

sudo certbot certonly --manual \
  --preferred-challenges dns \
  --manual-auth-hook /usr/local/bin/dns-add-record.sh \
  --manual-cleanup-hook /usr/local/bin/dns-remove-record.sh \
  -d example.com \
  -d "*.example.com" \
  --agree-tos \
  --email [email protected] \
  --non-interactive

提示: 使用 --manual-auth-hook--manual-cleanup-hook 后,DNS 验证可以完全自动化,支持自动续期。

5.9 DNS 验证排错

常见错误

错误 1:TXT 记录未生效

DNS problem: NXDOMAIN looking up TXT for _acme-challenge.example.com

解决方案:

# 检查记录是否已添加
dig TXT _acme-challenge.example.com +short

# 等待更长时间
# 检查 DNS 记录名称是否正确(不要包含根域名)
# _acme-challenge.example.com ✅
# _acme-challenge.example.com.example.com ❌

错误 2:API 凭证无效

Error finding zone for example.com: Unauthorized

解决方案:

# 检查凭证文件
cat /etc/letsencrypt/cloudflare/credentials.ini

# 确认权限
ls -la /etc/letsencrypt/cloudflare/credentials.ini
# 应该是 -rw------- (600)

# 验证 API Token 是否有效
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type:application/json"

错误 3:DNS 传播时间不足

DNS problem: DNS record for _acme-challenge.example.com did not propagate in time

解决方案:

# 增加传播等待时间
--dns-cloudflare-propagation-seconds 30

# 或手动确认记录已生效后再继续

5.10 DNS 验证最佳实践

  1. 使用 API Token 而非 Global API Key: 权限最小化,安全性更高
  2. 凭证文件权限: 设置为 600,仅 root 可读
  3. 合理设置传播等待时间: 根据 DNS 提供商的速度调整
  4. 使用自动化 Hook: 将手动 DNS 验证转化为自动化流程
  5. 测试续期: 使用 --dry-run 验证 DNS 插件的续期流程

注意事项

  1. 通配符证书必须使用 DNS 验证: HTTP-01 和 TLS-ALPN-01 都不支持通配符
  2. API Token 安全: 不要在脚本中硬编码 Token,使用凭证文件
  3. DNS 提供商锁定: 使用第三方 DNS 插件时,切换 DNS 提供商需要更换插件和凭证
  4. 多级通配符不支持: *.example.com 不能匹配 sub.www.example.com

扩展阅读