教程性质: 本文以 狼的提问箱 项目为例,但所有原理与步骤对任何「不备案、有海外用户、想让境内/境外各走最优线路」的站点通用。
场景速览:域名权威 DNS 托管在 DNSPod(免费版支持按「境内 / 境外 / 默认」线路返回不同解析结果);境内线路 CNAME 到腾讯 EdgeOne 国际站(免备案区域,大陆用户落到香港等最近节点,走腾讯跨境骨干,比 Cloudflare 免费版大陆绕行明显更快);境外/默认线路 CNAME 到 Cloudflare 优选 IP(经 Cloudflare for SaaS 自定义主机名识别流量,由既有 Cloudflare Tunnel 回源)。
1. 为什么能这么搭 —— 三个关键认知
整套方案成立的前提,是先把三个「概念解耦」想清楚:
1.1 Cloudflare for SaaS:让「流量识别」与「DNS 解析权」解耦
Cloudflare 普通接入(Free 套餐)要求把整域 NS 迁到 Cloudflare,这与「DNS 要放在 DNSPod 做分线路」天然冲突——NS 在谁手里,谁才说了算。
Cloudflare for SaaS(免费额度 100 个自定义主机名)绕开这个限制:
- 你有一个承载 zone(例如
cf-zone.example.net,托管在 Cloudflare); - 在承载 zone 里把目标域名(
box.example.com)加为 Custom Hostname,并验证归属; - Cloudflare 边缘从此「认识」这个域名:任何带着
Host: box.example.com到达 CF 边缘的请求,都会被匹配证书、按配置路由(本项目为 Tunnel 回源)。
关键结论:CF 侧只负责"来了流量怎么认、怎么转",不负责"流量怎么来"。流量能不能到 CF 边缘,取决于你自己的 DNS(DNSPod)把境外用户解析到哪。所以「境外走 CF」必须在 DNSPod 配一条境外线路指向 CF 的 IP/CNAME——CF 自己不会拉客。
1.2 DNSPod 分线路:免费就能「同一域名,不同答案」
腾讯云解析(DNSPod)免费版就支持按查询来源返回不同记录:线路可选「中国境内 / 境外 / 默认」,境内细分到省、运营商。默认线路是兜底,任何未命中的查询都走它。
要理解一个易错点:分线路的判定发生在 DNSPod 的权威服务器上,依据是「递归 DNS 服务器的出口 IP 归属地」——所以「境外用户走境外线路」成立的前提是境外用户的递归 DNS(8.8.8.8 等)能真的问到 DNSPod 权威,且没被中间网络劫持/污染(第 6 节有实战案例)。
1.3 EdgeOne 国际站:选对「加速区域」就不用备案
EdgeOne 分「中国大陆可用区 / 全球可用区(不含中国大陆)/ 海外可用区」等区域配置。官方口径:
- 选 全球可用区(不含中国大陆) 或海外区域 → 不要求 ICP 备案;
- 选中国大陆区域 → 必须备案。
不备案的代价:大陆用户没有本地节点,请求落到最近的海外节点(大陆一般落香港,也有日韩/新加坡等),由腾讯跨境骨干回源。实测体验通常仍明显优于 Cloudflare 免费版大陆线路(后者常绕美/日且抖动大)——所以「不备案也选 EO 当境内线路」是有实测前提的,切换前建议先测速对比。
补充:Cloudflare for SaaS 启用通常需要账户先绑定支付方式,额度内不扣费。
2. 目标架构
DNS 层 DNSPod(example.com 整域权威,免费版支持按线路返回)
NS = byron.dnspod.net / sunshine.dnspod.net(官方 NS)
box CNAME 记录
├─ 境内 ──▶ box.example.com.edgeone.example (EdgeOne 接入目标)
├─ 境外 ──▶ cf-pref.example.net (CF 优选 IP 域名)
└─ 默认 ──▶ cf-pref.example.net (兜底,语义同境外)
┌──────────────┴──────────────────────────────┐
大陆用户 ▶▶(命中境内线路) ◀◀ 海外用户(命中境外/默认线路)
▼ ▼
EdgeOne 国际站 Cloudflare 边缘
区域: 全球(不含中国大陆) 优选 IP: 198.51.100.x(示例段)
证书: TrustAsia, CN=box.example.com 识别: for SaaS 自定义主机名
回源: EO 直连源站公网端口 回源: Cloudflare Tunnel(纯出站)
│ │
└──────────────────┬──────────────────────────┘
▼
源站防火墙 iptables+ipset(仅放行 EO 回源网段;cloudflared 走 127.0.0.1 不受影响)
▼
HTTP 应用直接监听公网高位端口(本项目 TamaBox,无 nginx)
两条回源路径互不干扰:EO 是 入站直连(需开放端口 + 白名单锁定),CF 是 Tunnel 出站(无需任何入站规则)。
3. 前置条件清单
| # | 条件 | 说明 |
|---|---|---|
| 1 | 一个域名(example.com),权威 DNS 托管在 DNSPod | 免费版即可;注册局委派的 NS = byron/sunshine.dnspod.net |
| 2 | 一个 Cloudflare 账户 + 承载 zone(cf-zone.example.net) | 用于 for SaaS 与 Tunnel;域名本身的 DNS 不用迁给 CF |
| 3 | cloudflared Tunnel 已在跑 | 服务器 → CF 纯出站连接,绑定承载域 box.cf-zone.example.net |
| 4 | 服务器有公网 IP,且能开一个高位入站端口 | 给 EO 回源用;不需要 nginx,应用直接监听即可 |
| 5 | EdgeOne 账户(本例国际站 edgeone.ai) | 选免备案区域 |
| 6 | CF 账户已绑支付方式 | 启用 for SaaS 的前提(额度内不扣费) |
| 7 | 服务器 1C1G 级别即可 | iptables/ipset 是内核规则,无常驻进程 |
4. 分步实施
建议顺序:先 CF(步骤 A)→ 再 EO(步骤 B)→ 再 DNS(步骤 C)→ 最后服务器(步骤 D)。 前两步做完,流量仍在旧路径上,随时可暂停;步骤 C/D 才真正切换与收敛。
Step A —— Cloudflare 侧:SaaS 自定义主机名 + Tunnel(本次零改动,讲解原理)
本项目 box.example.com 的 SaaS 自定义主机名此前已配好(放在承载 zone cf-zone.example.net 下),Tunnel 已正常回源。若从零配置,动作是:
- 承载 zone
cf-zone.example.net→ SSL/TLS → Custom hostnames → Add custom hostname,填box.example.com; - 按提示完成归属验证(加一条 TXT 或 CNAME 验证记录);
- 在 Tunnel 的路由里把
box.example.com指到本地 HTTP 服务(http://127.0.0.1:端口); - 记录页里能拿到该主机名的 CNAME target(形如
<hash>.cdn.cloudflare.net)——它是「把流量引到 CF 边缘」的官方入口。
⚠️ 本项目实际没用 SaaS CNAME target,而是用优选 IP CNAME(
cf-pref.example.net):一个第三方维护的、解析到 CF 低延迟边缘 IP 的域名。原理相同——解析结果落在 CF 边缘,CF 靠Host: box.example.com识别出这是 SaaS 自定义主机名流量再按 Tunnel 路由。优选域名的可用性依赖第三方维护,失效时换回官方 CNAME target 即可(第 7 节维护清单)。本项目 DNSPod 上境外/默认线路指向的是
cf-pref.example.net。
Step B —— EdgeOne 侧:添加站点(CNAME 接入,免备案区域)
- EO 控制台 → 添加站点 → 填
box.example.com; - 接入方式选 CNAME 接入(⚠️ 不要选 NS 接入——NS 接入会要求把域名托管给 EO,与 DNSPod 分线路冲突,和「DNS 在 CF 手里没法分线路」是同一个问题);
- 加速区域选「全球可用区(不含中国大陆)」(免备案的关键选择);
- 归属验证:按 EO 提示添加验证记录(本项目用 TXT:
edgeonereclaim.example.com TXT ...),记录加在 DNSPod 的默认线路(CA/验证服务器多在境外,放默认线路全球可见最稳); - 配置回源:源站地址 = 你的公网 IP + 高位端口,Host 头 =
box.example.com;回源协议按你的源站监听协议选(应用直监听 HTTP 就选 HTTP,TLS 由 EO 边缘终结); - 免费证书:CNAME 验证通过 + 回源配置就绪后,EO 自动签发(本项目 TrustAsia,
CN=box.example.com)。签发需要几分钟到几小时——在证书签发完成前,443 会拿*.cdn.myqcloud.com兜底证书应答,浏览器会出现证书/HSTS 报错(第 6 节排障 6.1),属正常过程,等签发即可; - 开启「客户端 IP 头」(关键,默认关闭!):站点 → 网络优化 / 规则引擎 → 客户端 IP 头部 → 开启,自定义头部名必须填
EO-Client-IP——应用侧固定读取这个名字; - 删除/改写伪造的 CF 头:在 EO 加一条规则引擎规则,把客户端可能携带的
CF-Connecting-IP/X-Real-IP等头删掉——否则大陆攻击者经 EO 带假 CF 头,会被应用当成 CF 流量优先信任(头信任模型见 Step D)。
Step C —— DNSPod 分线路:三条记录
权威 DNS 在 DNSPod(整域 zone example.com),所以直接在整域 zone 里配,不需要任何子域委派。
| 主机记录 | 类型 | 线路 | 记录值 | TTL |
|---|---|---|---|---|
box | CNAME | 中国境内 | box.example.com.edgeone.example(EO 分配的目标) | 120~600 |
box | CNAME | 境外 | cf-pref.example.net(CF 优选 / 或 SaaS CNAME target) | 600 |
box | CNAME | 默认 | cf-pref.example.net(兜底,语义同境外) | 600 |
要点:
- EO 归属验证 TXT 保持「默认」线路(全球可见),别只放境内;
- 若之前误把 EO 的 CNAME 建在「默认」线路,会导致全球都走 EO——把它改成「中国境内」,境外/默认补 CF 两条;
- TTL 别太大,方便回滚与换优选域时快速生效。
Step D —— 服务器侧:应用监听 + 信任头 + 端口锁定
D1 应用直接监听公网端口(免 nginx)
本项目 TamaBox 自带 HTTP 服务器,改配置即可:
[server]
listen = 0.0.0.0:18080 ; 从仅本机 127.0.0.1 改为全网卡,EO 才能远程连入(18080 仅为示例端口)
- cloudflared 连的是
127.0.0.1:端口,0.0.0.0监听兼容回环连入,Tunnel 分支不受影响; - nginx 在本方案中从头到尾不是必需——防火墙(iptables/ufw)是内核规则,零常驻进程,比 nginx 轻得多,适合 1C1G 小机器。
D2 应用信任两家 CDN 的权威头
conf/app.ini:
[security]
trust_proxy = true
配合代码层支持(本项目 internal/shared/ip.go 的 ResolveClientIP),信任顺序:
CF-Connecting-IP(Cloudflare 权威头,海外分支);EO-Client-IP(EdgeOne 权威头,境内分支);- 兜底:TCP 对端 RemoteAddr。
⚠️ 头信任的安全前提(缺一不可):
- 源站防火墙只放行两家 CDN 的回源来源(EO 直连需要;CF 分支天然安全——Tunnel 是出站,外人到不了源站);
- EO 侧已删除伪造的 CF 头(Step B-8);
- 若还有 CF 侧直连,同样在 CF 规则里删除伪造的 EO 头。
任一缺失,「信任头」就会被攻击者直连源站 + 自带假头绕过——限流/封禁会拿到伪造 IP。
D3 端口白名单:iptables + ipset 自动维护(EO 免费档没有官方回源段)
EO 官方回源清单由「源站防护 / Origin ACL」提供(配套 DescribeOriginACL API),但该功能只支持付费套餐——免费档在控制台找不到入口,也没有可拉取的清单 URL。因此免费档采用**「种子清单 + 连接观察学习 + 定期同步」**的方案:
交付脚本 scripts/edgeone-origin-whitelist.sh(bash,需 root,架构要点):
- 白名单放 ipset 集合
EO_ORIGIN,iptables 一条规则引用它:自建链 EO_IN内「放行 127.0.0.0/8(保 cloudflared 回环)→ 命中集合放行 → 其余 REJECT tcp-reset」; - 更新走「临时集填充 →
ipset swap原子替换」:同步全程无规则清空窗口; - fail-safe:拉取失败 / 清单为空 / 全非法 → 拒绝更新、保留现网,绝不误删导致端口裸奔;
- 命令:
sync(周期入口)/learn(从连接表收集当前实际回源源 IP,做种子)/show/flush(回滚)/--dry-run; - 清单来源:本地文件(
/etc/edgeone-origin/cidr.txt)+ 可选 URL,取并集。
落地四步:
# 1. 改端口(默认 18080 → 你的实际端口)
# 2. 安装 ipset
apt-get install -y ipset
# 3. 生成种子:多跑几次 learn,把输出合并审阅后写入 cidr.txt(注意:种子是 EO 的"出口 IP",
# 不是应用日志里的客户端 IP!日志里是 EO-Client-IP 客户端真实 IP,不能当白名单)
bash edgeone-origin-whitelist.sh learn
bash edgeone-origin-whitelist.sh sync --dry-run
bash edgeone-origin-whitelist.sh sync
# 4. 验证:外网机器 nc -zv <IP> 18080 应 refused;https://box.example.com 应正常
# 5. 挂定时:cron「17 4 * * *」或 systemd timer,每日一次足够(EO 段 3~6 个月才变)
日后升级付费套餐:把
DescribeOriginACLAPI 的输出写入cidr.txt即可,脚本零改动。
5. 验证方法(含大陆 DNS 劫持陷阱)
5.1 分线路是否生效(🔴 重点:别在大陆本机 nslookup 境外 DNS)
大陆网络对发往境外公共 DNS(8.8.8.8 / 1.1.1.1 / 9.9.9.9 / OpenDNS,UDP 53 和 TCP 53 都会被劫持)的查询会返回按境内线路伪造的答案——在本机 nslookup box.example.com 8.8.8.8 永远显示 EO,即使境外线路早已配好。正确验证通道:
# 境内视角(本机直接查即可,通道可信)
nslookup box.example.com 223.5.5.5 # 期望 → CNAME edgeone.example → 203.0.113.x(EO 示例段)
nslookup box.example.com 119.29.29.29 # 同上(DNSPod 自家 DNS)
# 境外视角:用「境外服务器代为查询」,浏览器直接打开下面两个 API 之一
# https://api.hackertarget.com/dnslookup/?q=box.example.com
# https://networkcalc.com/api/dns/lookup/box.example.com
# 期望 → CNAME cf-pref.example.net → 198.51.100.x(CF 边缘 IP 示例段)
两条结果不一样 = 分线路生效。
5.2 HTTPS 证书是否就绪
echo | openssl s_client -connect box.example.com:443 -servername box.example.com \
| openssl x509 -noout -subject -issuer -dates
# 期望 subject = CN=box.example.com(而不是 *.cdn.myqcloud.com 兜底证书)
5.3 真实 IP 是否被正确识别
境内访问后看源站/EO 访问日志中的客户端 IP = 你的大陆公网出口 IP(说明 EO-Client-IP 生效);海外访问后同理应为你的海外出口 IP(CF-Connecting-IP)。
6. 今日排障实录(每个坑都值得记)
6.1 「网站使用 HSTS」报错 = EO 证书还没签发(不是配置错)
现象:DNS 切到 EO 后浏览器报 HSTS 错误页。
原因链:之前走 CF HTTPS 时应用下发了 Strict-Transport-Security: max-age=31536000; includeSubDomains(按 ExternalURL 以 https 开头就对每个响应下发)→ 浏览器记录「一年内必须 HTTPS」→ 切到 EO 后证书尚未签发、443 拿 *.cdn.myqcloud.com 兜底证书 → 证书与域名不匹配 → HSTS 错误页。
处理:等 EO 证书签发(几分钟~几小时)即可,域名不在 HSTS preload 列表,浏览器自动恢复,无需清缓存。紧急回滚 = DNS 改回 CF。
(若在 preload 列表里才是真麻烦——需先修好证书再等 preload 移除,故平时别主动提交 preload。)
6.2 误判「权威 DNS 在 Cloudflare」(方向性错误)
现象:一度以为 example.com 权威在 Cloudflare,推导出「需要子域 NS 委派」的复杂方案,差点让用户在 CF 做一堆不存在 zone 的操作。
根因:apex example.com 本身是一条 CNAME → your-project.pages.dev(Cloudflare Pages 项目)——nslookup 查 NS 时一路跟随 CNAME 链走到 pages.dev 的 NS(CF),被误读为「example.com zone 在 CF」。
正确做法:查域名权威必须问注册局(RDAP)或直查 SOA,不能信递归 CNAME 链:
# RDAP(注册局铁证):https://rdap.org/domain/example.com → 应返回 byron/sunshine.dnspod.net
# 或直查 SOA:nslookup -type=SOA example.com byron.dnspod.net
6.3 「境外也返回 EO」的假象 = 大陆 DNS 劫持(连 TCP 53 都劫)
现象:从大陆本机用 nslookup 测 8.8.8.8 / 9.9.9.9 / OpenDNS(UDP 和 TCP 都试),境外视角全部返回 EO,一度以为分线路没生效、甚至怀疑 DNSPod 配置错了。 真相:大陆网络劫持出境 DNS 查询,返回按境内线路伪造的 EO 答案,请求根本没到境外服务器。 破案手段:用境外服务器(hackertarget / networkcalc API)代为查询,立刻看到 CF 答案——DNSPod 三线路配置完全正确,从未出过错。教训见 5.1。
6.4 残留的阿里云验证记录
实测发现 DNSPod 上有历史残留 _dnsauth.box.example.com CNAME → xxxxxxxx...aliyunauth.certificate-validations.com(以前阿里云申请证书留下的)。_dnsauth / _acme-challenge 下的旧验证记录会干扰证书自动续期验证——证书验证记录的坑,建议确认证书正常后删除。
6.5 免费档没有「源站防护」,回源 IP 段拿不到
见 Step D3:免费档无官方清单,用 learn 观察法兜底;付费后切 DescribeOriginACL,脚本零改动。
7. 日常维护清单
| 周期 | 事项 |
|---|---|
| 证书到期前 | EO 免费证书自动续期并重新做域名验证——⚠️ CA 验证服务器多在境外,所有验证记录务必放默认线路;到期前留意 EO 控制台证书状态,失败会提示重新验证 |
| 每日(定时任务) | 白名单 sync(EO 回源段 3~6 个月才变,每日一次足够) |
| 每月 | 抽查 learn 输出是否有新回源源 IP 需补种;确认优选域名 cf-pref.example.net 仍解析到 CF 边缘(失效则换回官方 SaaS CNAME target,只改 DNSPod 一条记录) |
| EO 回源突然失败 | 先临时放行全段定位,再按 learn 结果收紧——EO 免费档收不到段变更通知,要有这个预案 |
| 长期 | 若升级 EO 付费套餐:启用源站防护拿官方段 → 接入 DescribeOriginACL 输出到 cidr.txt |
8. 回滚
- 快速回滚(境外也要回 CF 之外的兜底):DNSPod 把「境内」线路改指向 CF(或删掉境内线路,让默认兜底接管)→ 全球走 CF,分钟级生效;
- 彻底回滚(回到纯 CF 时代):删除 EO 站点/记录、撤防火墙放行、DNSPod 三线路合并为默认→CF;
- EO 证书到期前回滚,记得去 EO 控制台删除站点避免残留验证记录干扰。
附:配置快照(示例,2026-09-03 实测定稿,域名已脱敏)
域名: box.example.com(权威 DNS: DNSPod,NS=byron/sunshine.dnspod.net)
境内: CNAME box.example.com.edgeone.example → 203.0.113.x (EdgeOne 国际站,区域不含大陆,示例段)
境外/默认: CNAME cf-pref.example.net → 198.51.100.x (CF 优选 IP,示例段)
CF 侧: SaaS 自定义主机名(承载 zone cf-zone.example.net)+ Tunnel 出站回源
EO 证书: TrustAsia, CN=box.example.com(签发后约 90 天到期,自动续期)
EO 归属验证: edgeonereclaim.example.com TXT(DNSPod 默认线路 ✅)
EO 客户端头: EO-Client-IP(手动开启,头名固定 EO-Client-IP)
源站: TamaBox 直监听 0.0.0.0:高位端口(无 nginx);iptables+ipset 白名单
应用信任: trust_proxy=true;ResolveClientIP 信任 CF-Connecting-IP → EO-Client-IP → RemoteAddr
