Certbot 与 DNS-01:证书签发机制、通配符证书和自动续期
AI 参与说明(Agent:Codex):本文由 Codex 根据 Certbot、Let’s Encrypt 官方文档与 ACME 规范协助撰写并核对,资料整理于 2026-10-17。运行记录:模型
gpt-6.1-sol,reasoning effortultra,执行入口 Codex Desktop。命令使用示例域名,未申请真实证书或修改真实 DNS。
Certbot 是 EFF 提供的开源证书管理工具,能够通过 ACME 申请和续期 TLS 证书,常与 Let’s Encrypt 配合使用。本文重点介绍 DNS-01:通过在 DNS 中发布指定的 TXT 记录,证明申请方有权为某个域名申请证书。
DNS-01 适合申请通配符证书,也适合 Web 服务只在内网、无法接收公网验证请求的情况。长期运行时,应让 DNS 插件通过 API 自动完成验证,再由服务加载取得的证书。Certbot 官网、Let’s Encrypt challenge types
阅读前先认识这些词
| 英文术语 | 中文名称 | 简要解释 |
|---|---|---|
| Certbot | Certbot | 运行在申请方一侧的证书管理工具 |
| Let’s Encrypt | Let’s Encrypt | 提供证书签发服务的 CA |
| CA | 证书颁发机构 | 验证申请条件并签署证书的机构 |
| ACME | 自动化证书管理环境 | 客户端与 CA 协商验证、提交申请和获取证书的协议 |
| DNS-01 | DNS 验证挑战 | 要求申请方发布特定 TXT 记录的域名控制权验证方式 |
| TXT record | 文本记录 | DNS 中保存文本的一种记录类型 |
| authoritative server | 权威服务器 | 对某个 DNS 区域给出正式记录的服务器 |
| wildcard certificate | 通配符证书 | 包含 *.example.com 这类名称的证书 |
| CSR | 证书签名请求 | 提交给 CA 的申请,包含待认证的公钥和名称等信息 |
| deploy hook | 部署钩子 | 签发或续期成功后执行的命令,可用于让服务加载证书 |
Certbot、Let’s Encrypt 与 DNS 分别负责什么
Certbot is an ACME client. Let’s Encrypt is a CA that supports ACME. DNS-01 is a domain-control validation method used during certificate issuance. 这是对 Certbot User Guide 与 RFC 8555 的概括。
以 app.example.com 为例,各环节的职责如下:
| 环节 | 负责的事情 |
|---|---|
A / AAAA 或用于路由的 CNAME | 让访问者找到提供服务的地址 |
_acme-challenge.app.example.com 下的 TXT | 在 DNS-01 中提供本次验证所需的证明 |
| Certbot | 与 CA 交互,准备验证记录,管理私钥及证书文件 |
| Let’s Encrypt | 核验域名控制权等签发条件,签署证书 |
| Web 服务器或反向代理 | 加载证书及私钥,实际提供 HTTPS |
因此,签发成功和 HTTPS 配置完成是两个环节。DNS 插件属于 Certbot 的 authenticator;它完成域名验证,而 certonly 只取得证书,不会自动替你配置所有服务。Certbot 也能连接其他兼容 ACME 的 CA,并非只能使用 Let’s Encrypt。Certbot 插件说明
DNS-01 的工作机制
假设要为 example.com 申请证书。下面省略 ACME 账户注册、请求签名和轮询,只展示需要新验证时的签发流程。
flowchart TD
A["Certbot 提交 example.com 的申请"] --> B["CA 返回 DNS-01 challenge token"]
B --> C["Certbot 计算 TXT 验证值"]
C --> D["手动或通过 DNS API 发布 TXT"]
D --> E["等待公开 DNS 返回本次值"]
E --> F["CA 查询 _acme-challenge.example.com"]
F --> G{"返回值是否匹配?"}
G -->|匹配| H["验证通过,清理本次 TXT"]
G -->|不匹配| I["验证失败,检查记录与 DNS 可见性"]
H --> J["提交 CSR,取得并保存证书"]
J --> K["服务加载证书,提供 HTTPS"]
CA 给出随机 token 后,Certbot 将它与 ACME 账户公钥的指纹组成 keyAuthorization,再计算摘要:
keyAuthorization = token + "." + accountKeyThumbprint
TXT value = base64url(SHA256(keyAuthorization))
其中 accountKeyThumbprint 是按规范计算并编码的 JWK Thumbprint。发布的是这个验证值,不能直接把原始 token 当作 TXT,也不需要公开账户私钥或证书私钥。CA 按同样规则计算预期值,再查询 DNS 进行比对。RFC 8555 §8.1、§8.4
验证记录可写成下面的 DNS zone file 形式,内容仅作格式示意:
_acme-challenge.example.com. 300 IN TXT "<Certbot 显示的本次验证值>"
实际操作时直接使用 Certbot 给出的值,无须自己实现计算。手动模式需要按提示删除已完成验证的记录;DNS 插件通常会自动创建并清理本次记录。Certbot manual、Cloudflare 插件
Certbot 通常在本地生成证书私钥,通过 CSR 向 CA 提交公钥等申请内容。CA 签署的是证书,服务端随后使用本地私钥参与 TLS;私钥不会作为 DNS-01 的证明发布到 DNS。RFC 8555 §7.4
不需要网站公网可达,但验证记录要能公开查询
DNS-01 does not require an inbound connection to the machine running Certbot. The CA must be able to resolve the challenge record through public DNS. Certbot 官网、RFC 8555 §8.4
例如,nas.example.com 的服务只在内网运行,仍可通过公开 DNS 的 TXT 记录完成验证。Certbot 所在机器需要能访问 CA,以及自动化时使用的 DNS API,但不用为这项验证开放本机的 80、443 或 53 入站端口;负责公开 DNS 的 authoritative server 则必须可被外部查询。
只修改 /etc/hosts、只在内网 DNS 中加 TXT,不能满足这项公开验证。取得证书也不会让内网服务自动变成公网服务。这里讨论的是自己控制的公开域名,不能套用到任意自造的内部名称。Let’s Encrypt DNS-01
通配符与根域名需要分清
| 申请的名称 | DNS-01 查询位置 | 证书名称覆盖范围 |
|---|---|---|
example.com | _acme-challenge.example.com | 根域名本身 |
app.example.com | _acme-challenge.app.example.com | 这个子域名 |
*.example.com | _acme-challenge.example.com | app.example.com 等一层子域名 |
*.example.com 不覆盖 example.com,也不覆盖 a.b.example.com。因此,常见申请会同时包含 example.com 和 *.example.com。Let’s Encrypt 的通配符签发使用 DNS-01,HTTP-01 无法满足这项验证。Certbot wildcard 说明、Let’s Encrypt challenge types
这两个名称的验证位置相同;如果同时需要两个 challenge,应在同一位置保留两个 TXT 值,不能让后一条覆盖前一条。等相关验证全部完成,再清理本次记录。Let’s Encrypt DNS-01
使用前:选择真正管理 DNS 的服务商
插件需要对接的是当前 authoritative DNS 托管商,不一定是购买域名的注册商。例如,域名在某个注册商购买、NS 已委派到 Cloudflare 时,应修改 Cloudflare 中生效的记录。
可以先用 dig 查看 NS,再与 DNS 控制台核对。以下查询命令要求环境已安装 dig,将示例域名替换为自己控制的域名:
dig NS example.com +short
手动验证与 DNS 插件是两种替代方案,选择其中一种即可。长期运行优先考虑支持自动验证的 DNS 插件。插件需要单独安装,并且与 Certbot 使用同一套安装环境;不要混用 snap 的 Certbot 和另一 Python 环境里的插件。Certbot DNS plugins
方式一:手动发布 TXT,理解完整验证过程
前提是已安装 Certbot,并能修改所申请域名的公开 DNS。运行:
sudo certbot certonly \
--manual \
--preferred-challenges dns \
--cert-name example.com \
-d example.com \
-d '*.example.com'
certonly 表示只取得证书;--manual 让你手动完成验证;--preferred-challenges dns 选择 DNS-01。--cert-name 固定 Certbot 中的证书名称,-d 可重复指定名称。通配符参数用引号包住,避免 shell 将 * 展开。Certbot manual、证书名称
首次运行还会要求处理账户信息和服务条款。出现 TXT 提示后,按以下顺序操作:
- 在 DNS 控制台添加 Certbot 指定的 TXT 名称和值。有些控制台会自动补上
example.com,此时名称只填_acme-challenge;最终完整名称必须与提示一致。 - 如果收到同名的第二个 TXT 值,追加记录,保留仍在验证中的第一个值。
- 等待记录在公开 DNS 中可见,再按 Certbot 提示继续,不要添加后立即回车。
- 签发完成后,按提示清理本次 TXT。
等待时可从另一个终端查询:
dig TXT _acme-challenge.example.com +short
dig @1.1.1.1 TXT _acme-challenge.example.com +short
返回结果应包含提示中的本次值,若需要两个值则都应存在。公共 resolver 查到记录是排查依据,不能保证 CA 所用查询路径已经获得相同答案;还要考虑 authoritative server 的同步、缓存和负缓存。持续失败时,应查询 NS 所指向的 authoritative server。Let’s Encrypt DNS propagation 说明
手动完成一次验证,不等于已经配置自动续期。 未提供自动认证 hook 的 --manual 证书不能无人值守续期;需要再次按提示操作,或配置 --manual-auth-hook / --manual-cleanup-hook 完成创建、等待与清理。长期使用时,优先采用已有的 DNS 插件。Certbot manual renewal
方式二:用 Cloudflare DNS 插件自动验证
本节适用于 DNS 托管在 Cloudflare、支持 snap 且已准备好 snapd 的 Linux 主机。其他 DNS 服务商应使用对应插件,不能只替换凭据内容就继续沿用 Cloudflare 的参数。下面是供目标服务器执行的示例,本文未执行安装或真实签发。
安装 Certbot 与插件
先检查已有安装,避免不同来源的 certbot 混用;已有证书的主机迁移安装方式时,应先保留配置并阅读官方安装说明。对于尚未安装 Certbot 的这类 Linux 环境,可按官方 snap 路径执行:
sudo snap install --classic certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare
确认 /snap/bin 已加入 PATH,并检查实际执行文件及插件:
command -v certbot
certbot --version
sudo certbot plugins
插件列表应包含 dns-cloudflare。其中 trust-plugin-with-root=ok 确认插件拥有与 Certbot snap 相同的权限,应只安装可信插件。Certbot 官方 snap / Wildcard 安装说明
准备受限的 API Token
按照插件文档创建 Cloudflare API Token,授予所需 zone 的 Zone:DNS:Edit,并将资源范围限定到要申请证书的 zone。使用这种受限 Token,避免使用权限覆盖整个账户的 Global API Key。这里仍是 zone 级 DNS 修改权限,不能理解成只允许修改 _acme-challenge 一条记录。Cloudflare 插件 credentials
例如,在服务器的 /root/cloudflare.ini 中保存凭据。先设权限,再编辑真实值:
sudo touch /root/cloudflare.ini
sudo chmod 600 /root/cloudflare.ini
sudoedit /root/cloudflare.ini
文件内容如下,占位符必须在服务器本地替换,真实 Token 不应进入文章、Git 或命令历史:
dns_cloudflare_api_token = REPLACE_WITH_YOUR_SCOPED_API_TOKEN
Certbot 保存凭据文件路径供后续续期使用,因此文件应保持可读、路径稳定,Token 也要持续有效。Cloudflare 插件 credentials
申请根域名与通配符证书
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/cloudflare.ini \
--dns-cloudflare-propagation-seconds 60 \
--cert-name example.com \
-d example.com \
-d '*.example.com'
插件会通过 API 创建所需 TXT,等待指定时间,完成验证后清理记录。60 秒是本例采用的等待参数,不是传播时间保证;应按实际 DNS 可见性调整。插件当前文档的默认值为 10 秒。Cloudflare 插件参数与示例
若要先验证流程,可在上述申请命令中加入 --dry-run,使用 Let’s Encrypt staging 完成测试,不保存测试证书;测试仍会真实调用 DNS API。确认成功后,再运行不带 --dry-run 的命令取得正式证书。Certbot testing options
取得证书之后:配置服务与自动续期
先查看 Certbot 实际管理的证书名称、覆盖域名、到期时间和路径:
sudo certbot certificates
本例使用 --cert-name example.com,默认配置下的重要文件是:
| 文件 | 用途 |
|---|---|
/etc/letsencrypt/live/example.com/fullchain.pem | 服务端证书及中间证书链 |
/etc/letsencrypt/live/example.com/privkey.pem | 与证书对应的私钥,应限制访问 |
服务应引用 live 下的稳定路径;续期后 Certbot 会更新这些路径所指向的文件。若服务位于另一主机、容器或设备上,还要安排安全分发和加载,不能只更新 Certbot 所在机器。Certbot certificate files
检查自动续期任务
对通过自动 DNS 插件取得的证书,测试续期:
sudo certbot renew --cert-name example.com --dry-run
预期结果是该证书的模拟续期成功。测试仍可能修改真实 DNS 的验证记录,需要有效的 API Token。安装方式可能已经配置 systemd timer 或 cron;应检查已有任务的启用状态和日志,避免重复添加调度。例如,在 systemd 环境查看:
systemctl list-timers --all
renew 会判断证书是否需要续期;应定期运行检查,不要将 --force-renewal 当作日常定时参数。Certbot renewal、官方自动续期说明
CA 可以复用仍有效的 authorization,并非每次续期都必然重新发布 TXT;一旦需要新的 DNS-01 challenge,就必须使用本次给出的值。永久保留第一次的 TXT 无法替代后续验证。RFC 8555 §7.1.3
让服务加载新证书
证书文件更新后,服务可能仍在内存中使用旧证书。对于本机已安装 Nginx、由 systemd 管理且路径已经核对的环境,可以添加一个 deploy hook。以下脚本只是该部署条件下的示例,其他服务需改用对应加载方式。
先创建并编辑脚本文件:
sudo install -d -m 755 /etc/letsencrypt/renewal-hooks/deploy
sudoedit /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
写入:
#!/bin/sh
set -eu
/usr/sbin/nginx -t
/usr/bin/systemctl reload nginx
设为可执行并测试:
sudo chmod 700 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
sudo certbot renew --cert-name example.com --dry-run --run-deploy-hooks
Certbot 会在成功签发或续期后执行 deploy 目录中的可执行 hook;这个目录中的脚本会影响其他证书的成功签发或续期,因此只应放置该主机通用的操作。普通 --dry-run 默认不执行 deploy hook;加上 --run-deploy-hooks 才会在测试成功后执行,并使用当前正式证书,不会让服务加载 staging 证书。测试前应先让服务使用正式证书,并确认脚本中的命令路径正确。Certbot hooks、testing options
最后检查实际服务:客户端访问时收到的证书名称和到期时间应与预期一致。只看到 renew 返回成功,还不能证明远端代理或设备已经加载了新证书。
常见问题与 DNS 边界
| 现象 | 优先检查 |
|---|---|
| TXT 已添加,但 CA 查不到 | 是否修改了生效的 DNS 托管商;控制台是否重复补全域名;authoritative server 和缓存是否已更新 |
| 根域名成功、通配符失败 | 是否覆盖了同名 TXT;是否仍使用 HTTP-01;是否有单独的 CAA 限制 |
| 本地查到记录,部分验证仍失败 | authoritative server 是否同步,是否存在查询路径差异、负缓存或 DNSSEC 故障 |
| 手动签发成功,定时续期失败 | 是否仍用无认证 hook 的 --manual;是否具备自动修改 DNS 的能力 |
| 插件运行时报权限或认证错误 | API Token 的 zone 范围、DNS 修改权限、有效性及凭据文件路径 |
| 续期成功,客户端仍收到旧证书 | 实际 TLS 入口是否加载新文件;是否遗漏分发、容器挂载或 reload |
DNSSEC 和 CAA 与 DNS-01 有什么关系
DNSSEC 认证 DNS 数据的来源与完整性;DNS-01 利用查询到的 TXT 证明域名控制权。启用 DNSSEC 不是这项验证的必要条件,但配置错误的 DNSSEC 会造成 SERVFAIL,使 CA 无法完成查询。RFC 8555 §11.2、Let’s Encrypt SERVFAIL 排查
CAA 指定哪些 CA 获准为域名签发证书。如果已有 CAA 排除了 Let’s Encrypt,即使 TXT 正确也可能无法签发。issuewild 可单独约束通配符签发;缺少 issuewild 时适用 issue。应先核对已有策略,再调整,不能把添加 TXT 当成绕过 CAA 的方式。Let’s Encrypt CAA
可以把验证委派给另一个 DNS 区域吗
Let’s Encrypt 支持通过 CNAME 或 NS 委派完成 DNS-01,例如把 _acme-challenge 交给专门的验证区域管理。这可以将签发所需的 DNS 操作集中到单独区域。Let’s Encrypt DNS-01 delegation
CA 能跟随委派,不代表所选 Certbot 插件会自动找到并修改委派目标。部署前应单独核对插件行为,必要时采用适配的 hook;不能只加一条 CNAME,就认定前面的 Cloudflare 命令能跨区域自动工作。Certbot 官方功能讨论 #6566
关联阅读
- DNSSEC 介绍:DNS 签名、信任链与验证实践:继续理解 DNS 数据认证、DNSSEC 状态与
SERVFAIL排查。
参考资料
- Certbot User Guide:插件、手动验证、续期、文件路径与 hooks;本次读取的是 5.8.0 文档,部署时以实际安装版本核对参数。
- Certbot 安装说明:本例 Linux snap 与 DNS 插件安装路径。
- certbot-dns-cloudflare:API Token、凭据文件、传播等待与命令示例。
- Let’s Encrypt challenge types 与 CAA:验证方式及 DNS 侧限制。
- RFC 8555:ACME:账户、authorization、CSR 与 DNS-01 验证规则。