跳到主要内容
ecdsa.com

HTTP-01 vs DNS-01:如何选择 ACME 挑战类型

两者都是向证书颁发机构证明你控制着某个域名。一个通过 Web 服务器证明,另一个通过 DNS 证明,这决定了你能拿到哪些证书,以及必须保管哪些凭证。

用 ACME(RFC 8555)自动化管理证书时,首先要决定的就是用 HTTP-01 还是 DNS-01。使用 HTTP-01 时,证书颁发机构通过 80 端口从你的 Web 服务器获取令牌;使用 DNS-01 时,它在你的 DNS 区域中查询 TXT 记录。两者都是《基线要求》(Baseline Requirements)规定的域名验证方法,得到的证书也完全相同,但它们能验证的对象、能运行的环境以及可能出错的地方各不相同。

概览

HTTP-01DNS-01
规范RFC 8555 §8.3;《基线要求》§3.2.2.4.19RFC 8555 §8.4;《基线要求》§3.2.2.4.7
证明的内容对响应该域名的 Web 服务器的控制权对 DNS 区域或被委托的 _acme-challenge 名称的控制权
证明放在哪里http://name/.well-known/acme-challenge/token_acme-challenge.name 处的 TXT 记录
内容密钥授权(token.thumbprint)密钥授权 SHA-256 摘要的 base64url 编码(43 个字符)
通配符域名不允许允许
IP 地址证书允许(RFC 8738)不支持
网络要求入站 TCP 80 端口;Let's Encrypt 最多跟随 10 次重定向,目标可以是 80 或 443 端口只要求你的权威 DNS 服务器可达
内部主机或防火墙后的主机否是
多台服务器或任播每个节点都必须提供令牌,或把请求代理到同一个响应器每个域名一条记录,与服务器无关
服务器上的机密除 Web 根目录的写权限外没有DNS API 凭证(用 CNAME 委托限定其范围)
典型故障80 端口被封锁、重定向循环、CDN 或 WAF 代为响应、过时的 AAAA 记录记录尚未传播到辅助服务器、DNSSEC 配置损坏、残留记录、API 限额

CA 实际检查什么

两种挑战都使用密钥授权(key authorization):用一个点把挑战令牌和你的 ACME 账户密钥的 JWK 指纹(RFC 7638)连接起来。由于账户密钥是证明的一部分,看到令牌的人无法用它为自己的账户完成挑战。HTTP-01 原样提供密钥授权;DNS-01 则发布它的 SHA-256 摘要,摘要足够短,可以放进一条 TXT 记录。下面的脚本针对 RFC 7638 中的示例账户密钥和 RFC 8555 中的示例令牌,复现了这两个值(Node.js 26)。

计算 HTTP-01 响应和 DNS-01 记录
import { createHash } from "node:crypto";

// Account public key: the RSA example JWK from RFC 7638 §3.1
const accountJwk = {
  kty: "RSA",
  e: "AQAB",
  n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
};

const b64url = (buf) => buf.toString("base64url");
const sha256 = (s) => createHash("sha256").update(s).digest();

// RFC 7638: hash the required members in lexicographic order
const { e, kty, n } = accountJwk;
const thumbprint = b64url(sha256(JSON.stringify({ e, kty, n })));
console.log("account key thumbprint:", thumbprint);

// http-01 (RFC 8555 §8.3): serve the key authorization itself
const httpToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0";
console.log("\nhttp-01");
console.log(`  GET http://www.example.org/.well-known/acme-challenge/${httpToken}`);
console.log(`  body: ${httpToken}.${thumbprint}`);

// dns-01 (RFC 8555 §8.4): publish base64url(SHA-256(key authorization))
const dnsToken = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA";
const txt = b64url(sha256(`${dnsToken}.${thumbprint}`));
console.log("\ndns-01");
console.log(`  _acme-challenge.www.example.org. 300 IN TXT "${txt}"`);
输出
account key thumbprint: NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs

http-01
  GET http://www.example.org/.well-known/acme-challenge/LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0
  body: LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0.NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs

dns-01
  _acme-challenge.www.example.org. 300 IN TXT "ZTRx1Ckl1-tM05o5zaizTTA0yUy5AGereMgSNWC6Ll8"

算出的指纹与 RFC 7638 §3.1 中公布的值一致,说明计算无误。客户端会替你完成这一切;但用 curl 或 dig 手动排查失败的挑战时,了解这些值会很有帮助。

多视角验证与 DNSSEC

自2025年起,公共 CA 必须从多个网络位置确认每一次验证(《基线要求》§3.2.2.9)。自2026年6月15日起,至少有四个远程视角参与验证,它们彼此相距至少 500 公里,分布在至少两个区域互联网注册中心的服务区域内,并且最多只允许其中一个结果不一致;2026年12月15日起,最低数量提高到五个。对 HTTP-01 来说,这意味着该域名的每个任播节点、每个 GeoDNS 应答以及每个 IPv4 或 IPv6 地址都必须提供令牌。对 DNS-01 来说,在你请求 CA 验证时,每台权威域名服务器都必须已经有这条记录。

自2026年3月15日起,CA 在进行域名验证查询和 CAA 查询时还必须验证 DNSSEC(SC-085v2 提案)。未签名的区域不受影响,但如果已签名区域的签名已过期或 DS 记录有误,那么无论使用哪种挑战,现在都无法签发证书;DNSSEC 查看器可以显示 CA 将要检查的信任链。

把 DNS 凭证的权限降到最低

DNS-01 的主要风险在于它所需要的凭证:放在 Web 服务器上的 DNS API 令牌往往能改写整个区域。CA 用普通的 DNS 解析查询 TXT 记录,而普通解析会跟随 CNAME 和 NS 委托,因此可以让 _acme-challenge 名称指向一个单独的区域,服务器只能写入这个区域,别的都不能写:

只委托挑战名称
_acme-challenge.example.com.  3600  IN  CNAME  example-com.acme.example.net.

这样一来,借助 acme-dns 之类的工具,或者 DNS 服务商提供的、权限限定在单条记录或单个区域的令牌,就能限制凭证被盗后可能造成的影响。再配合使用 RFC 8657 参数的 CAA 记录——自2027年3月15日起,CA 必须处理这些参数:

CAA:一个 CA、一个账户、仅限 DNS 验证
example.com.  IN  CAA  0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=dns-01"
example.com.  IN  CAA  0 issuewild "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=dns-01"

设置 validationmethods 之后,即使攻击者短暂控制了你服务器上的 80 端口或某个 Web 路径,也无法再通过 HTTP-01 获取证书。CAA 记录生成器可以生成这些记录。

TLS-ALPN-01 与持久 DNS 记录

TLS-ALPN-01(RFC 8737)在 443 端口上的 TLS 握手中完成验证,使用 acme-tls/1 协议和一张携带密钥授权摘要的自签名证书。它适合关闭了 80 端口的 TLS 终止代理和负载均衡器;与 HTTP-01 一样,它可以验证 IP 地址,但不能验证通配符域名。

DNS-PERSIST-01 免去了每次续期都要修改 DNS 的步骤。SC-088v3 提案(2025年10月)把“DNS TXT Record with Persistent Value”作为《基线要求》§3.2.2.4.22 加入:在 _validation-persist.name 处放置一条长期有效的 TXT 记录,写明 CA 和你的 ACME 账户 URI,还可以附加 policy=wildcard。对应的 ACME 挑战仍是 IETF 草案。Google Trust Services 自2026年9月28日起支持草案 01;Let's Encrypt 只在测试环境(staging)中提供该挑战,并且截至2026年10月,在草案中一个悬而未决的问题解决之前,暂不在生产环境中上线。代价是账户密钥变成了一项长期有效的授权,因此要像保护 DNS 凭证一样保护它。

很快每次续期都要重新验证

授权的复用期限正随着证书有效期一起缩短。《基线要求》目前把域名验证结果的复用期限上限定为 200 天,2027年3月起为 100 天,2029年3月起为 10 天。Let's Encrypt 的默认配置文件目前把授权复用 30 天,2027年2月10日起缩短为 10 天,2028年2月16日起缩短为 7 小时。无论选择哪种挑战,都必须能无人值守地运行;手动复制 TXT 值的做法撑不过2028年。

如何选择

  • 一台装有 ACME 客户端的公开 Web 服务器:HTTP-01。
  • 通配符证书:DNS-01(如果你的 CA 提供 DNS-PERSIST-01,也可以用它)。
  • 使用公开域名的内部主机、仅限 VPN 访问的服务、开发环境:DNS-01;注意这些域名会出现在 CT 日志中。
  • 负载均衡的服务器集群、多区域或任播部署:DNS-01,或者使用 HTTP-01,并让每个节点把 /.well-known/acme-challenge/ 代理到同一个响应器。
  • CDN 和托管平台:交给平台完成验证(通常是通过其边缘节点的 HTTP-01,或者由你一次性添加的 CNAME)。
  • IP 地址证书:HTTP-01 或 TLS-ALPN-01;在 Let's Encrypt 只能通过有效期为六天的 shortlived 配置文件申请。
  • 因策略要求关闭了 80 端口:DNS-01 或 TLS-ALPN-01。

迁移说明

  1. 从 HTTP-01 迁移到 DNS-01:创建一个专用的挑战区域,用 CNAME 把每个 _acme-challenge 名称指向该区域,给 ACME 客户端一个权限限定在该区域的令牌,然后切换客户端的挑战类型;现有证书继续有效。
  2. 响应挑战前先检查传播情况:大多数客户端都能等到所有权威服务器都返回该记录后再继续;应启用这一功能,而不是固定等待一段时间。
  3. 清理记录:客户端应在验证后删除 TXT 记录(RFC 8555 §8.4);残留记录会不断累积,拖慢查询。
  4. 用 CAA 锁定,同时为 issue 和 issuewild 设置 accounturi 和 validationmethods。
  5. 按 CA 的时间表续期:在支持的地方使用 ACME 续期信息(ARI,RFC 9773),否则在有效期过去约三分之二时续期;证书有效期计算器可以按日期显示各项上限。
  6. 在证书透明度日志中监控证书签发(面向开发者的证书透明度),这样通过任何方式获得的证书都能被你看到。

参考资料

全部对比