需求
一个子域名 sub.example.com,想要国内和海外走不同的线路:
- 海外:走 Cloudflare CDN,回源到海外源站
- 国内:走国内 CDN(我这里用腾讯云 EdgeOne),不过 CF
前提是:主域名 example.com 继续托管在 Cloudflare,其余所有记录一条都不动。只把这一个子域名的解析权单独挪出去。
全流程我做成了个小工具,不想手动走的可以直接跳到文末「顺手做了个工具」,或者看 splitdns 使用教程。
为什么 Cloudflare 单独搞不定
两个坎:
- CF 权威 DNS 不支持普通记录按地区给不同答案。原生方案只有 Load Balancing 的 Geo Steering,付费附加组件。
- 记录一旦是橙云,CF 返回的就是 anycast IP,全球都一样,DNS 层面根本没得分。
所以只能把这一个主机名交给支持线路的 DNS 服务商(DNSPod)。但这就带出第二个坎:
委派出去之后,CF 就不再是这个主机名的权威 DNS 了,橙云、缓存、WAF、Universal SSL 对它全部失效。那海外线还怎么走 CF CDN?
答案是 Cloudflare for SaaS(自定义主机名)。它本来是给 SaaS 厂商代理客户自有域名用的,特点恰好是「DNS 不在 CF 也能让 CF 代理这个主机名」。Free 版就能用。
整体结构
example.com (Cloudflare)
├── www / mail / 其它所有记录 ──> 照旧,完全不受影响
└── sub NS ──> DNSPod ← 只有这一条被委派出去
│
├── 境内线 ──> CNAME sub.example.com.eo.dnse2.com (EdgeOne)
└── 默认线 ──> CNAME fallback.mycdn.net (CF 橙云) ──> 海外源站
下面把 SaaS 用的第二个域名叫 mycdn.net、海外源站 IP 叫 203.0.113.10,自行替换。
需要三样:主域名在 CF、另一个也在 CF 的域名(做 SaaS 区)、一个 DNSPod 账号。自定义主机名不能和 SaaS 区同名,所以第二个域名是必须的。
第一步:配 Cloudflare for SaaS(在 mycdn.net 这个区做)
SSL/TLS → 自定义主机名,先启用 Cloudflare for SaaS。
1. 建回落源(Fallback Origin),必须是本区内的橙云记录:
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | fallback |
203.0.113.10 |
Proxied |
回到自定义主机名页面,把 fallback.mycdn.net 填进「回退源」,等状态变 有效。面板上那句提示不是摆设:回退源没到 Active,自定义主机名验证不会通过。
2. 添加自定义主机名:填 sub.example.com,证书验证方式选 TXT。
别选 HTTP 验证。HTTP 验证要求域名已经指向 CF 才能过,而这时你还没切 DNS,会变成先有鸡还是先有蛋。
添加完 CF 会给你几条 TXT,全部记下来:
_cf-custom-hostname.sub.example.com—— 主机名归属验证,一条_acme-challenge.sub.example.com—— 证书 DCV,可能是两条(证书带通配符 SAN 时,基础域名和*.sub.example.com各要一条,主机记录同名、值不同,两条都得加)
签出来的证书是 CF 代签的证书(我这边签出来的有 SSL.com 也有 Google Trust Services),三个月有效期,到期自动续,不用管。
3.(可选)给单个主机名指定不同的源服务器
回退源是所有自定义主机名的默认去处。如果某个主机名要走另一台机器,可以在它的「编辑」里单独填「源服务器」和「源服务器 SNI 值」——这个功能现在 Free / Pro / Business 都能用了,以前是企业版专属。
这里有个很坑的点:源服务器一旦不是默认回退源,那台机器必须能认得这个 SNI 名字,否则一律 403。
原因是 CF 回源时,Host 头是 sub.example.com,但 TLS 握手用的 SNI 是你填的「源服务器 SNI 值」(比如 ks-5.mycdn.net)。Traefik / Nginx 这类按 Host / SNI 路由的网关匹配不到对应的 router,就直接把请求打回来了。
解决办法很简单:在那台机器上给这个名字随便挂一个 router / vhost 就行,指向哪个服务、有没有 SSL 证书都无所谓,有这么个条目让它能路由到就够了。不加的话你会对着一个莫名其妙的 403 查半天。
另外注意:单独指定「源服务器 SNI 值」是企业版 SSL for SaaS 才开放的字段,非企业账号填了会被 1456 拒掉。但绝大多数情况根本不用填——CF 默认就拿源服务器主机名当回源 SNI,留空即可。
第二步:在 DNSPod 建区并配好记录(先建好,最后才委派)
DNSPod 支持直接把子域名当独立域名添加:在「添加域名」处填 sub.example.com(免费版就行)。
如果主域名不在这个腾讯云账号下,DNSPod 会要求先加一条 TXT 证明归属(报
QuhuiTxtNotMatch)。那条 TXT 是加在主域名上的,也就是还在 CF 的example.com。
建好后进域名详情,看它分配给你的 NS(免费版一般是 f1g1ns1.dnspod.net / f1g1ns2.dnspod.net,付费版是 ns3.dnsv2.com 之类,以控制台实际显示的为准)。
新加的域名默认是「暂停解析」状态,记得去域名列表里开启。不开的话委派、记录可以全对,解析就是不出结果,很难看出问题在哪。
加记录,主机记录都是相对 sub.example.com 这个区而言的:
| 主机记录 | 类型 | 线路 | 记录值 |
|---|---|---|---|
@ |
CNAME | 默认 | fallback.mycdn.net |
@ |
CNAME | 境内 | sub.example.com.eo.dnse2.com(EdgeOne 给的 CNAME) |
_cf-custom-hostname |
TXT | 默认 | CF 给的值 |
_acme-challenge |
TXT | 默认 | CF 的 DCV 值之一 |
_acme-challenge |
TXT | 默认 | CF 的 DCV 值之二(通配符 SAN 那条) |
几个要点:
- CF 那条放「默认」线,不要放「境外」线。 默认线是兜底,任何没匹配到境内的解析器都有答案;只配境内 + 境外的话,某些识别不了归属的解析器会拿不到记录。
- 同名两条
_acme-challengeTXT 是正常的,都是 CF 要的,别以为加重复了删掉一条——少一条证书签不出来。EdgeOne 那边如果也要验证,是它自己的另一套记录。 - DNSPod 免费版只有「默认 / 境内 / 境外」三条线路,没有国家级线路,要精确到某个国家得专业版以上。国内一套、海外一套的话免费版够用。
@上放 CNAME 是非标准用法,DNSPod 允许,但会和同名的其它记录类型冲突。如果它拒绝,见文末替代方案。- TTL 填 600 秒。DNSPod 免费版最低就是它,填更小会被接口直接拒掉。
国内线走 EdgeOne 的话,证书由 EO 托管,不用自己在源站上折腾——按 EO 自己的流程把域名加进去、走完它的验证就行。
第三步:在 Cloudflare 上做 NS 委派
回到 example.com 这个区。
1. 先处理掉 sub 以及 *.sub 下面所有已有记录。
别偷懒。留着不报错,但它们会变成 shadowed records——记录还在列表里看得见,实际一条都不生效。排查的时候能把人整疯。
注意是「处理」不是「直接删」:如果这些记录还在服务(比如 api.sub.example.com 指着某个后端),要先把它们搬到 DNSPod 的新区里去,再从 CF 删。直接删就把线上打断了。
2. DNS → 记录 → 添加记录,加两条 NS:
| 类型 | 名称 | 内容 | TTL |
|---|---|---|---|
| NS | sub |
f1g1ns1.dnspod.net |
自动 |
| NS | sub |
f1g1ns2.dnspod.net |
自动 |
代理状态显示「仅 DNS」是正常的,NS 类型本来就没有橙云开关。Free 版就能加 NS 记录做委派。
这一步只影响 sub 这一个名字,example.com 的其它记录、NS、注册商那边的设置统统不用动。
顺带说一句:CF 面板里那个 Enterprise-only 的 Subdomain setup 是另一回事——那是把子域作为独立 zone 托管在 CF 自己这边。委派给第三方用不到它。
3. DNSSEC:example.com 开了 DNSSEC 的话,子区不签名就不用管(正常的 insecure delegation);子区也签名了才需要在 CF 加一条 sub 的 DS 记录。
4. glue 记录:不需要。只有 NS 主机名本身在 sub.example.com 之下(比如 ns1.sub.example.com)才需要 glue。
第四步:验证
看委派有没有生效:
dig +trace sub.example.com
直接问 DNSPod 的 NS:
dig @f1g1ns1.dnspod.net sub.example.com
看证书和是否过 CF:
curl -sIv https://sub.example.com 2>&1 | grep -E 'subject|issuer|cf-ray'
最后回 CF 的自定义主机名页面,等到证书状态:有效且主机名状态:有效。委派 + TXT 生效通常几分钟。
踩坑清单
委派之后,CF 对这个主机名不再有权威。 主域名区里的 WAF 规则、缓存规则、页面规则、Universal SSL 都不覆盖它了。海外线的 HTTPS 由自定义主机名的证书提供,走的是 mycdn.net 那个区的 SSL/TLS 设置和规则——要配规则去那个区配,别在主域名区里找。
自定义源服务器不是解析目标。 解析要指向 SaaS 区里的橙云记录(把流量带进 CF),源服务器是 CF 收到之后往哪转。写反了 CF 根本不认识那个名字。
用了自定义源服务器就去源站上补 SNI 对应的 router,见第一步第 3 点,不然只会看到 403。
自定义主机名的「最低 TLS 版本」默认是 TLS 1.0,介意的话记得手动调到 1.2。
海外源站的防火墙要放行 CF 回源 IP,SSL/TLS 模式在 mycdn.net 区里设 Full (strict)。
如果国内线不是走 CDN 而是直连源站,那台机器要自己持有 sub.example.com 的证书,而且 DNS-01 续期的 _acme-challenge TXT 得加在 DNSPod 而不是 CF,已有的自动续期脚本记得改 provider。
操作顺序别反:先建好 DNSPod 的区和全部记录,最后一步才在 CF 加 NS、删旧记录。这样切换瞬间就有解析,中间不断。
替代方案:不做委派
如果 DNSPod 的 @ CNAME 不好使,或者不想动 NS,还有更轻的做法:
主域名区里 sub 保留成灰云 CNAME,指向 sub.geo.另一个放在DNSPod的域名,把分线路配在那个名字上。
sub CNAME sub.geo.mydns.net 仅 DNS(灰云)
效果一样(GeoDNS 判定依据是递归解析器的位置 / EDNS Client Subnet,多一跳 CNAME 不影响),还绕开了 apex CNAME、DNSSEC、shadowed records 这一堆事,改回橙云就能回滚。
先试这个,真不行再上 NS 委派。
顺手做了个工具
上面这套流程环节多、每步都要等生效,域名一多就靠人记不住,于是做成了工具:
源码:github.com/woodchen-ink/splitdns(MIT)
下载:GitHub Releases
详细用法:splitdns 使用教程(带截图)
Windows 与 macOS 桌面版:
- Windows 安装版 —— 数据在
%APPDATA%\splitdns,升级重装不动它 - Windows 绿色版 —— 解压即用,数据在 exe 同级
data/,整个文件夹拷走就带走全部配置 - macOS —— universal,Intel 与 Apple Silicon 通用,数据在
~/Library/Application Support/splitdns;没做签名公证,首次打开右键 → 打开,或xattr -dr com.apple.quarantine splitdns.app
它干这几件事:
配置向导——把上面四步拆成带验证的步骤。能自动做的直接调 API(建回退源、加自定义主机名、建 DNSPod 域名并启用解析、写 DCV TXT、配分线路记录、加 NS 委派),做不了的给出精确到「填什么值」的指令。关键是平台接口返回成功不算数,巡检查到真的生效才算完成——所以关掉程序隔天回来进度还在,会按当前真实状态重新判定;已经通过的步骤如果线上被人改了会自动退回「等待生效」并说明差在哪。
巡检——随时核对三个平台的实际状态,专门盯这些容易翻车的地方:
- 父区里被委派遮蔽的记录(那些看得见但不生效的)
- CF 要的验证 TXT 少没少(通配符 SAN 那两条同名不同值最容易漏)
- CF 那条有没有挂在「默认」线兜底
- DNSPod 上域名是不是还处于暂停状态
- 委派的 NS 与 DNSPod 实际分配的对不对得上
- 证书状态、到期时间、自定义源服务器是否与配置一致
清理那一步会先迁移——被遮蔽不等于该扔。它逐条判断:接入域名本身只删不搬(落点归线路配置管),其余原样搬去 DNSPod 再删,橙云记录会单独提示「搬过去就是直连了」。
不联网上传任何东西,密钥只存在本机,程序也不监听端口。需要自备 Cloudflare API Token(Zone:Read + DNS:Edit + SSL:Edit)和腾讯云 SecretId / SecretKey。