记一次DNS配置错误导致的证书续签失败

记一次DNS配置错误导致的证书续签失败

February 8, 2026

写在前面

我刚从深圳回来的那天晚上,还没到宿舍,有人说铜锣湾的证书过期了。 后来发现是因为DNS配置错误,导致证书自动续签失败了。 我回到宿舍,花了可能三个小时才解决,今天又整理了一下才完全搞明白怎么回事。

对我来说,这个错误解决并不是一帆风顺的, 它涉及到 DNS 的一些 edge case,以及Lego(用来自动续签证书的程序)的一些特殊机制。 因此在这里写一下。

背景知识

对于专业的人来说,可能我说的 DNS 的 edge case 其实不是那么 edge。下面我会先说一下这个情况,如果你觉得这平平无奇,说明你就是那个专业人士,剩下的不用看了。

要查询一个域名的 SOA 记录,例如 dig @1.1.1.1 aaa.bbb.org SOA,得到的结果会有三种情况。

第一种情况,如果 aaa.bbb.org 如果本身就是一个 zone 的顶部,那么会正常返回它的 SOA。例如:

$ dig @1.1.1.1 chn.moe SOA
; <<>> DiG 9.20.15 <<>> @1.1.1.1 chn.moe SOA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16306
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;chn.moe.                       IN      SOA

;; ANSWER SECTION:
chn.moe.                1800    IN      SOA     adelaide.ns.cloudflare.com. dns.cloudflare.com. 2396040839 10000 2400 604800 1800

;; Query time: 470 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Feb 08 21:00:26 CST 2026
;; MSG SIZE  rcvd: 102

这里 chn.moe. 1800 IN SOA adelaide.ns.cloudflare.com. dns.cloudflare.com. 2396040839 10000 2400 604800 1800 就是这个域名的 SOA 记录。

第二种情况,如果 aaa.bbb.org 本身不是一个 zone 的顶部,并且也没有 CNAME 到别的域名,那么返回的查询结果(answer)应该是空的;但服务器往往也会顺便把这个 zone 的顶部域名的 SOA 记录一起放到响应中返回,同时又并不算是一个 answer。例如:

$ dig @1.1.1.1 aaa.chn.moe SOA
; <<>> DiG 9.20.15 <<>> @1.1.1.1 aaa.chn.moe SOA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8057
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;aaa.chn.moe.                   IN      SOA

;; AUTHORITY SECTION:
chn.moe.                1800    IN      SOA     adelaide.ns.cloudflare.com. dns.cloudflare.com. 2396040839 10000 2400 604800 1800

;; Query time: 490 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Feb 08 21:04:39 CST 2026
;; MSG SIZE  rcvd: 106

注意这里 chn.moe. 1800 IN SOA adelaide.ns.cloudflare.com. dns.cloudflare.com. 2396040839 10000 2400 604800 1800 出现在 AUTHORITY SECTION 而不是 ANSWER SECTION,以及 ANSWER: 0 而不是 ANSWER: 1

第三种情况是,如果 aaa.bbb.org CNAME 到别的域名,那么返回的查询结果中会带上 CNAME 过去的那个域名的 SOA;即,CNAME 对 SOA 也有效果。例如:

$ dig @1.1.1.1 matrix.chn.moe SOA
; <<>> DiG 9.20.15 <<>> @1.1.1.1 matrix.chn.moe SOA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7139
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;matrix.chn.moe.                        IN      SOA

;; ANSWER SECTION:
matrix.chn.moe.         300     IN      CNAME   autoroute.chn.moe.
autoroute.chn.moe.      60      IN      SOA     vps6.chn.moe. chn.chn.moe. 2023010100 7200 3600 1209600 3600

;; Query time: 506 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Feb 08 21:07:24 CST 2026
;; MSG SIZE  rcvd: 112

在上面的例子中,如果递归查询 autoroute.chn.moe 的 SOA 时,服务器返回 SRVFAIL,那么会发生什么呢?对 matrix.chn.moe 的查询也会 SRVFAIL。

报错与解决

我的域名 chn.moe 的 DNS 配置,比起大多数个人网站来说,稍微复杂。

  • 绝大多数域名直接在 Cloudflare 上配置。只做 DNS,不代理 HTTP 请求。
  • 在我自己的服务器上运行 CoreDNS 并把少数几个域名(例如这次出问题的 autoroute.chn.moe )的 NS 记录设置为这个服务器,以实现一些 Cloudflare DNS 没有或者不能免费用的功能(例如,根据来源 IP 分流)。
  • 少数域名(例如这次出问题的 xn--s8w913fdga.chn.moe )虽然托管在 Cloudflare 上,但是 CNAME 到 autoroute.chn.moe

xn--s8w913fdga.chn.moe 的证书快要到期时,Lego 会调用 Cloudflare 的 API,将 _acme-challenge.xn--s8w913fdga.chn.moe 的 TXT 记录修改为由 Let’s Encrypt 指定的一个随机字符串。随后,Let’s Encrypt 调用公共 DNS 发现解析与自己提供的字符串一致,就可以确认域名的所有权并签发证书。

续签失败的报错是这样的:

acme: renewalInfo endpoint indicates that renewal is needed
acme: Trying renewal with 0 hours remaining
acme: Obtaining bundled SAN certificate
AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz/1770186497/652530223586
acme: Could not find solver for: tls-alpn-01
acme: Could not find solver for: http-01
acme: use dns-01 solver
acme: Preparing to solve DNS-01
acme: Cleaning DNS-01 challenge
acme: cleaning up failed: cloudflare: could not find zone for domain "xn--s8w913fdga.chn.moe": [fqdn=_acme-challenge.xn--s8w913fdga.chn.moe.] unexpected response for 'xn--s8w913fdga.chn.moe.' [question='xn--s8w913fdga.chn.moe. IN  SOA', code=SERVFAIL]

具体来说:Lego 要调用 Cloudflare API 修改 _acme-challenge.xn--s8w913fdga.chn.moe 的解析,需要知道这个域名属于 Cloudflare 的配置中,这个账户下面的哪个 zone。Lego 中并没有选项,使得用户可以明确告诉 Lego 这个域名属于哪个 zone(可能是不想麻烦用户);它也没有调用 Cloudflare API 来查询(不知道是不是没有这个 API);它选择自己去探测,并且在我的配置下,在探测的过程中会崩溃。这个探测的过程就是:依次查询 _acme-challenge.xn--s8w913fdga.chn.moe xn--s8w913fdga.chn.moe chn.moe 的 SOA,直到返回的 answer 非空(忽略 answer 以外的响应),以及 answer 没有 CNAME;如果这个过程中出现了 SRVFAIL,那么马上放弃。源代码在这里

当时,我的 CoreDNS 只配置了 A 记录,并没有配置 SOA 记录;这导致向它查询 SOA 记录时失败,进一步导致查询 xn--s8w913fdga.chn.moe 的 SOA 失败,进一步导致续签失败。

解决办法说来也简单:把 SOA 记录补上就行了。

最后更新于