通配 DNS 不命中深层子域:一层只匹配一个 label

`*.example.com` 配了 A 记录,但 `a.b.example.com` 还是 NXDOMAIN。DNS 通配只匹配一个 label——这里给修复路径。

你配了通配 DNS:*.example.com10.0.0.1foo.example.com 正常解析。但 a.b.example.com(更深的子域)返回 NXDOMAIN。这不是 bug,而是规则:DNS 通配只匹配一个 label,从不一次匹配一串。*.example.com 覆盖 foo.example.com,但不覆盖 foo.bar.example.com。这由 RFC 4592 定义,而且关键是——通配连自己的子域都不覆盖:*.example.com 不会匹配任何位于 *.example.com 之下的名字。

最快修复: 每多一层需要覆盖,就为那个确切的父级再加一条通配。要让 *.bar.example.com 生效,就在 *.example.com 之外再建一条 *.bar.example.com 记录。如果你在 Cloudflare 上开了代理(橙云),第一层以下的通配还需要 Advanced Certificate Manager,边缘证书才真正覆盖它(下文细说)。如果子域是有限且已知的列表,干脆别用通配,直接加显式 A/CNAME 记录。

dig +short foo.example.com           # 解析成功 -> 第一层通配生效
dig +short foo.bar.example.com       # NXDOMAIN -> bar.example.com 这一层没有通配

先判断你属于哪一类

症状最可能的原因跳转
单层子域解析,多层 NXDOMAIN通配只覆盖一个 label原因 1,修复 1-3 步
DNS 解析但浏览器报证书错证书 SAN 不含更深的名字原因 4,修复第 5 步
DNS-only 正常,开代理就坏(Cloudflare)边缘证书不覆盖嵌套通配原因 5
部分用户失败,部分正常这一层缺 AAAA(IPv6)原因 6
一个 resolver 解析、另一个不解析缓存陈旧 / 尚未生效修复第 6 步

常见原因

按命中率从高到低。

1. 通配只覆盖一个 label

*.example.com 匹配:

  • foo.example.com
  • bar.example.com

匹配:

  • foo.bar.example.com
  • a.b.c.example.com

按 RFC 4592,星号只替代单个 label,并且通配会”挡住自己”——它永远不会为树中位于自己之下的名字合成应答。

怎么判断:单 label 子域能用,再深一点就返回 NXDOMAIN 或错的值。

dig +short foo.example.com            # 解析
dig +short foo.bar.example.com        # NXDOMAIN 或错的值

2. 每一层都需要自己的通配

要覆盖更深的层,就加一条父级正好是那一层的通配:

*.example.com       A    10.0.0.1
*.bar.example.com   A    10.0.0.1

*.bar.example.com 覆盖 x.bar.example.com,但同样不覆盖 x.y.bar.example.com。如果你真的需要每一个深度,就得沿每条分支一直往下加通配——这正是为什么拉平层级(修复第 4 步)通常是更好的答案。

怎么判断:多租户 app 客户能创建嵌套子域,但你只加了最顶层那条通配。

3. 不同提供商差在记录管理,不在匹配规则

通配的匹配算法由 RFC 4592 标准化,合规的 resolver 在哪都一样。真正有差异的是提供商工具:有的面板允许你直接填 *.bar,有的要求先建好中间区域,有的禁止在代理/CDN 记录上用通配。如果一条深层通配在一家提供商”能用”、另一家不行,几乎总是因为能用的那边有一条你忘了加的显式记录——而不是匹配规则变了。

怎么判断:从两家导出 zone 文件做 diff。能用的那份 zone 会多一条通配或显式记录。

4. SSL 证书不覆盖更深的名字

哪怕 DNS 解析了,通配 TLS 证书也只覆盖一个 label。Let’s Encrypt 的 *.example.com 证书验证 foo.example.com,但不验证 foo.bar.example.com——后者需要单独的 *.bar.example.com 证书。截至 2026 年 6 月,Let’s Encrypt 签发通配仍然要走 ACME 的 DNS-01 challenge(HTTP-01 无法签发通配)。

怎么判断:DNS 解析,但浏览器报 NET::ERR_CERT_COMMON_NAME_INVALID。确认证书的 SAN 列表:

echo | openssl s_client -connect foo.bar.example.com:443 -servername foo.bar.example.com 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

如果 SAN 只显示 *.example.com,那更深的名字就没被覆盖。

5. Cloudflare 代理不覆盖嵌套通配

Cloudflare 明确说”wildcards are only supported on the first label”(通配只支持第一层 label)。你可以代理更深的通配——在 Name 字段填 *.www 来覆盖 *.www.example.com——但坑在证书。Cloudflare 的 Universal SSL 只覆盖根域和第一层子域。要代理第一层以下的通配(*.www.example.com),你需要 Advanced Certificate Manager,边缘才有匹配的证书;否则即便 DNS 解析,橙云请求也会 TLS 失败。

怎么判断:DNS-only(灰云)下能用,一开代理就抛 525/握手错误或证书告警。

6. 缺 IPv6 的 AAAA 记录

你加了 *.example.com A 但没加 AAAA。偏好 IPv6 的客户端在更深的子域上失败,纯 IPv4 客户端则正常。

怎么判断:部分用户能用、部分不能。

dig +short AAAA foo.bar.example.com   # A 记录解析、AAAA 为空

最短修复路径

第 1 步:摸清子域到底有几层

foo.example.com          → 1 层
bar.foo.example.com      → 2 层
baz.bar.foo.example.com  → 3 层

每一层想覆盖,都需要它自己的通配或显式记录。

第 2 步:每层加一条通配

*.example.com               A    10.0.0.1
*.foo.example.com           A    10.0.0.1
*.bar.foo.example.com       A    10.0.0.1

繁琐,但严格遵守”一个 label”规则。多数面板里 Name 字段只填左半部分(**.foo*.bar.foo),区域会补上其余部分。

第 3 步:或给已知子域加显式记录

如果集合有限(不是用户生成),就枚举它——没有通配歧义,SSL 也更简单:

foo.example.com               A    10.0.0.1
bar.foo.example.com           A    10.0.0.1
baz.foo.example.com           A    10.0.0.1

第 4 步:多租户 app 拉平层级

customer.team.app.example.com 这种就重构成 customer-team.example.com。一条单 label 通配(*.example.com)就覆盖所有租户,一张通配证书也全覆盖。对用户生成的子域,这是最干净的长期方案。

第 5 步:拿到匹配每一层的证书

跨多层的 TLS 需要下面之一:

  • 多张通配证书(*.example.com *.foo.example.com),每张都用 Let’s Encrypt 的 DNS-01 challenge 签发。
  • 一张 multi-SAN 证书,显式列出每一层。
  • 在 Cloudflare 上,用 Advanced Certificate Manager 覆盖第一层以下的代理通配。

签发后验证:

echo | openssl s_client -connect baz.bar.foo.example.com:443 \
  -servername baz.bar.foo.example.com 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

第 6 步:用多个 resolver 测

for resolver in 8.8.8.8 1.1.1.1; do
  for sub in foo bar.foo baz.bar.foo; do
    echo -n "$sub.example.com @$resolver: "
    dig @$resolver +short "$sub.example.com"
  done
done

每行都应返回你的 IP。空行说明那一层仍没有通配或记录(或者改动还没生效——按父区域的 TTL 等一等,通常 300-3600 秒)。

怎么确认已修好

  1. dig +short a.b.example.com 在至少两个 resolver(8.8.8.81.1.1.1)上都返回你的 IP。
  2. curl -sI https://a.b.example.com 返回 HTTP 状态码(而不是连接或 TLS 错误)。
  3. openssl s_client 的 SAN 检查列出一条能覆盖 a.b.example.com 的通配。
  4. 如果在 Cloudflare 上代理,同一条 curl 在橙云开启时也能成功,而不只是灰云。

常见问答

为什么 *.example.comfoo.example.com 生效、对 foo.bar.example.com 不生效? 通配只替代单个 label(RFC 4592)。foo 是一个 label,所以命中;foo.bar 在通配之下有两个 label,没有可合成的东西,于是返回 NXDOMAIN

*.example.com 是不是覆盖 bar.example.com 以及它下面的所有内容? 不是。它覆盖 bar.example.com 这种直接子级,但不覆盖 x.bar.example.com。通配从不匹配位于自己之下的名字——那一层需要单独的 *.bar.example.com 记录。

一张通配 SSL 证书能不能覆盖所有深度? 不能。*.example.com 证书只覆盖 foo.example.com。每深一层(*.bar.example.com)都需要它自己的通配证书,或一张列出这些名字的 multi-SAN 证书。Let’s Encrypt 通配必须走 DNS-01 challenge。

嵌套通配不开代理时正常,挂到 Cloudflare 后面就坏,为什么? Universal SSL 只覆盖根域和第一层子域。第一层以下的代理通配(*.www.example.com)需要 Advanced Certificate Manager,边缘才会出示匹配的证书。

有没有办法不用一层一层维护通配? 有——拉平层级。把 team.customer.example.com 换成 team-customer.example.com,这样一条 *.example.com 记录加一张证书就全覆盖。对有限列表,显式记录也比嵌套通配更简单。

预防建议

  • 设计子域层级时记住”一个 label”规则;尽量控制在 <= 2 层。
  • 多租户 app 用单 label 子域(customer-team.example.com,而不是 team.customer.example.com)。
  • 每个通配层都要同步规划好对应的 SSL 策略。
  • 加新子域层时,把 DNS、证书 SAN、CDN/代理配置一起审一遍。
  • 在 DNS 笔记里记下通配结构,免得后来的工程师踩坑。

相关阅读

标签: #排查