申请通配符或多域名证书时,ACME DNS-01常见的第一句报错就是 NXDOMAIN。它不是"TXT值不对"的同义词,而是查询链路里连这个名字都没有找到。先把重试按钮放一边:查错名称,重试只是在给DNS服务器增加心理负担。本文把验证域名、CNAME委托、权威NS、TXT传播和DNSSEC拆开,定位到哪一层再修哪一层。
一、NXDOMAIN到底说明了什么
DNS-01要求客户端把客户端根据token与账户密钥派生出的验证值放在 _acme-challenge.待验证域名 的TXT记录中,ACME服务器再查询该名称并比对值。Let's Encrypt官方文档明确了这个名称和TXT验证方式;RFC 8555也把dns-01定义为由ACME服务器查询DNS记录完成验证。
NXDOMAIN的重点是"名称不存在",和NOERROR但没有TXT、SERVFAIL、超时不是一回事。不同解析器可能缓存负结果,所以改完记录后不能只看本机一次查询。

二、先确认ACME真正查询的名称
最容易犯的错是把待签发域名直接加TXT,或者通配符写成带星号的查询名。验证 *.example.com 时,常规DNS-01查询仍围绕 _acme-challenge.example.com,不是 _acme-challenge.*.example.com。实际名称以ACME客户端日志和订单授权对象为准,不要凭记忆拼接。
bash
DOMAIN=example.com
NAME="_acme-challenge.${DOMAIN}"
printf 'query=%s\n' "$NAME"
dig +noall +answer "$NAME" TXT
dig +noall +authority "$NAME" TXT
第一条看答案,第二条看权威区是否返回SOA。若查询名写错,后面的CNAME和TXT检查都没有意义。生产脚本应把最终规范化FQDN写入日志,避免把根域、子域和通配符混成一锅。
三、区分NOERROR空答案与NXDOMAIN
两者在排障上完全不同:NXDOMAIN通常表示权威服务器认为该名字不存在;NOERROR/NODATA表示名字存在,但没有所请求类型的记录。若父域存在而子域不存在,权威响应里的SOA通常能帮助判断否定缓存边界。
bash
dig +noall +comments +authority _acme-challenge.example.com TXT
dig +trace _acme-challenge.example.com TXT
# 只把实际返回的状态写入诊断日志,不用grep到空输出就判定成功
dig +noall +answer _acme-challenge.example.com TXT
不要只执行 dig TXT 后看"没有输出"。保存 status、ANSWER、AUTHORITY 和 SERVER,才能区分没有记录、被权威拒答和本地递归缓存异常。+trace适合定位委派链路,不代表ACME服务器一定使用同一个递归解析器。
四、沿权威NS一路查,别被本地缓存带偏
先查域名的NS,再直接询问权威服务器。权威服务器没有记录而公共递归仍有旧TXT,说明缓存尚未过期;权威已有记录而本地仍NXDOMAIN,则优先检查负缓存、查询线路和DNSSEC。
bash
ZONE=example.com
dig +short NS "$ZONE"
for ns in $(dig +short NS "$ZONE"); do
printf "\nserver=%s\n" "$ns"
dig +noall +comments +answer +authority \
@"$ns" _acme-challenge."$ZONE" TXT
done
本例验证区域根域;查子域时单独指定NAME,不能把查询名改成区域根域。ZONE应为实际权威区域,子区委派需用+trace确认。若DNS服务商控制台显示记录,但权威NS回答没有它,常见原因是改错了账户、区域或记录名;若权威回答正确,再看递归缓存和传播,不要继续改记录。
五、CNAME委托时查目标,不要同时塞CNAME和TXT
如果把 _acme-challenge.example.com CNAME到专用验证域,TXT通常应放在CNAME目标上。DNS名称不能同时把同名CNAME和其他数据记录当成普通并列项使用;委托目标也必须再追到它自己的权威NS。
bash
NAME=_acme-challenge.example.com
dig +noall +answer "$NAME" CNAME
TARGET=$(dig +short "$NAME" CNAME | sed 's/\.$//')
if [ -n "$TARGET" ]; then
dig +noall +answer "$TARGET" TXT
dig +noall +authority "$TARGET" TXT
fi
这里有个很隐蔽的坑:CNAME目标末尾的点表示完整域名;API填值时有的服务商要求相对名,有的要求FQDN。最终以权威查询结果为准。若历史TXT残留,先确认是否属于仍在进行的并发验证,再按服务商语义删除,不能用"清空全部TXT"这种粗暴方案。
六、DNSSEC、SERVFAIL和传播延迟怎么分层
NXDOMAIN修正后变成SERVFAIL,方向可能已经从记录名转到DNSSEC、签名过期或委派不一致。对比普通解析器、权威NS和带DNSSEC校验的结果;不要把SERVFAIL当作TXT值错误。
| 响应现象 | 通常含义 | 先做什么 |
|---|---|---|
| NXDOMAIN | 权威认为名称不存在 | 查验证名称、区域和委派 |
| NOERROR无TXT | 名称存在但没有TXT | 查记录类型、目标和生效区 |
| SERVFAIL | 解析链或DNSSEC失败 | 查权威状态、签名和委派 |
| NOERROR有旧TXT | 能查到但值不匹配 | 保留并发值,核对token后重试 |
DNS传播不是一个固定秒数。TTL影响缓存,但负缓存还受SOA中的否定缓存参数影响;权威服务器、递归解析器和CA验证器看到的时间可能不同。修改后等待并重复查询,比连续创建新订单更稳妥。
七、用三种方案做中立对照
纯命令行可以直接调用DNS Provider API,acme.sh等开源客户端也能通过DNS API自动写入TXT;平台化方案则通常把订单、权限、重试和多目标部署统一管理。无论选哪种,核心验收都一样:写入的是正确验证名称,权威NS能查到正确TXT,验证完成后按策略清理旧值。本文只作技术边界对照,不把某个方案写成唯一答案。参考:Let's Encrypt DNS-01文档、acme.sh开源仓库 与 CertbotX。
八、续期前验收清单
- 从ACME日志取出准确授权域名,确认没有把星号写进查询名。
- 递归查询与至少一台权威NS分别读取TXT,并记录status、ANSWER和AUTHORITY。
- 存在CNAME时只沿目标查TXT,确认目标区域和末尾点语义。
- 把NXDOMAIN、NOERROR空答案、SERVFAIL和旧TXT分开处理。
- 确认验证完成后再清理对应TXT,不删除并发订单仍需要的值。
- 最终用ACME staging或受控测试订单复验,再接入自动续期和告警。
事实依据:RFC 8555 第8.4节、Let's Encrypt Challenge Types文档。线上证书是否真正更新,还要另行读取部署节点的证书指纹和有效期;DNS查询成功不等于Nginx已经加载新证书。