目 录CONTENT

文章目录

Cloudflare Tunnel + SaaS 优选加速完整指南

Administrator
2026-04-21 / 0 评论 / 1 点赞 / 87 阅读 / 0 字

Cloudflare Tunnel + SaaS 优选加速完整指南

一句话总结:Cloudflare Tunnel 默认分配的入口 IP 在国内访问很慢。本方案用 Cloudflare for SaaS 把你的业务域名"挂"到一个自己挑选的优选 IP 入口上——DNS 还是解析到 Cloudflare,但连的是你挑过的快节点,进站之后再由 SaaS 回退源把流量送进隧道。本文以 Halo 博客(HTTP 8090)+ Jitsi Meet(HTTPS 8443) 两个内网服务为例,一次配好、顺便支持日后扩展。

🏢 公司 + 家里两台设备都跑 cloudflared?先跳到《进阶三:两地部署》——分清「一条隧道双连接器」还是「两条隧道」,再动手。


📌 为什么要折腾这一套

问题在哪:

Cloudflare Tunnel 可以免公网 IP、免开端口地把内网服务(Halo 博客、Jitsi Meet、Home Assistant……)暴露到公网。但它给每个隧道分配的入口是 xxx.cfargotunnel.com,解析出来的 Cloudflare IP 是随机分配的——这套 IP 对国内用户来说经常又慢又不稳定。

解决思路:

  1. Cloudflare 的所有边缘节点共享同一套服务,只要能连上任意一个优质节点,就能访问你的服务;
  2. 优选 IP:通过测速工具(如 CloudflareSpeedTest)或公共优选域名,找出对你网络最快的 Cloudflare IP;
  3. Cloudflare for SaaS:允许你在 Cloudflare 上注册"自定义主机名",让边缘节点认得你的业务域名——于是可以把业务域名的 DNS 偷偷指到优选 IP 上,节点收到请求后照样能把流量送回你的隧道。

🧠 先看懂原理(配置前必读)

流量是怎么走的

flowchart TD
    U["👤 用户访问 https://meet.example1.com"] -->|"① DNS 解析:meet → ip → 优选 IP"| E["🌐 Cloudflare 边缘节点(优选 IP)"]
    E -->|"② TLS 握手 SNI = meet.example1.com<br>边缘匹配到自定义主机名"| F["🔁 回退源 tunnel.example2.com"]
    F -->|"③ cloudflared 按 Host 头匹配规则<br>(blog.example1.com 则命中另一条规则)"| S["🖥 Jitsi Meet localhost:8443<br>Halo 博客 localhost:8090"]
  • ① DNS 决定你连到哪台 Cloudflare 服务器——优选 IP 在这一步起作用;
  • ② SNI 决定 Cloudflare 把请求送给谁——SaaS 自定义主机名在这一步起作用;
  • ③ 隧道负责按 Host 头把请求分发到对应服务——cloudflared 是从内网主动外连 Cloudflare 的,所以不需要公网 IP。

💡 记住这句就抓住了全文题眼:DNS 决定连到哪,SNI 决定送给谁,Host 头决定进哪个端口。

全部名字一览(2 个服务 + 2 个设施)

双域名方案下,两个服务一共只涉及 4 个子域名字:

名字所在域名角色DNS 记录云朵对应服务会有人直接访问它吗
blog.example1.comexample1.com(主)业务域名①CNAME → ip.example2.com⚪ 灰Halo 博客 :8090✅
meet.example1.comexample1.com(主)业务域名②CNAME → ip.example2.com⚪ 灰Jitsi Meet :8443✅
ip.example2.comexample2.com(辅)优选入口CNAME → 优选域名(或 A → 优选 IP)⚪ 灰全服务共用❌ 没人,DNS 链条中的指路牌
tunnel.example2.comexample2.com(辅)回退源CNAME → xxx.cfargotunnel.com(Tunnel 自动生成)🟠 橙全服务共用❌ 没人,Cloudflare 内部回传用

📌 先澄清:这不是要你买四个域名! 上表全部是子域名——子域名不用花钱注册,在 DNS 页面加一条记录就诞生一个。"双域名方案"指的是你只需要注册两个顶级域名:主域名放业务,辅助域名放设施。

共用与独用的边界:业务层每服务一套(blog、meet 各自一条自定义主机名,不能共用);设施层全局一套(ip、tunnel 所有服务共用一条,命名永远用中性名,不要带服务名)。

💡 记忆口诀:blog / meet 给人看,ip 来指路,tunnel 是 Cloudflare 的内部通道。 设施层建好就忘掉,浏览器里敲的、发给别人访问的,永远只有业务域名。

端口绑定写在哪?——Tunnel 的规则里

回退源不绑定任何端口。真正把"主机名 → 内网端口"对应起来的,是 Tunnel 的 Public Hostname 列表(cloudflared 按请求的 Host 头逐条匹配):

Tunnel Public Hostname(主机名规则)服务说明
tunnel.example2.com → http://localhost:8090兜底回退源的门牌,service 指哪个服务都行——只有直连 tunnel.example2.com 的人会看到它,SaaS 流量不走这条
blog.example1.com → http://localhost:8090Halo业务规则①,blog 的流量命中这条
meet.example1.com → https://localhost:8443(No TLS Verify)Jitsi业务规则②,meet 的流量命中这条

两个还是三个子域名?

回退源和业务地址两个角色省不掉,区别只在于要不要 ip 这个中间层。可以省:让业务子域的 CNAME 直接指向优选域名(或优选 IP),第 4 步与第 5 步合并为一步,其余步骤不变。权衡如下:

两个子域(省掉 ip)三个子域(推荐)
结构业务子域直接 CNAME → 优选域名业务子域 → ip → 优选域名
初次配置少建一条记录多一步,多一个"忘建/忘灰云"的出错点
更换优选目标改业务记录本身,动的是业务记录,改错即业务中断只改 ip,业务记录零风险
多服务每个服务各自指向优选,换一次改 N 条所有服务共用 ip,换一次只改 1 条
自动优选工具(cfnb-go 等)只能接管业务记录本身,多服务需跑多个实例完美契合:工具只碰 ip,业务记录永久免维护

怎么选:单个服务 + 手动维护 → 两个够用;要用自动优选工具,或有多个服务 → 用三个。优选层的所有变化(手动换、自动换、失效回滚)都发生在 ip 上,业务记录永远不动,这就是中间层的价值。

为什么业务域名和 ip 必须是灰云?

灰云(仅 DNS)意味着 Cloudflare 只负责域名解析,不接管流量代理。这样用户的浏览器会直接拿着优选 IP 去连 Cloudflare 边缘,并且 TLS 握手时 SNI 是 meet.example1.com——边缘节点靠这个 SNI 匹配到你注册的自定义主机名。

如果开了橙云,Cloudflare 会把它当成普通站点重新接管 TLS 和回源,与 SaaS 方案冲突,容易出现证书错误、ERR_TOO_MANY_REDIRECTS 或 CNAME 循环。


🛠 准备工作

  • 两个域名:主域名 example1.com(放业务)+ 辅助域名 example2.com(放设施,必须托管在 Cloudflare);主域名只需能添加 CNAME 和 TXT 记录
  • cloudflared 已在一台内网机器上安装并登录
  • 两个内网服务已就绪:Halo 博客 http://localhost:8090、Jitsi Meet https://localhost:8443(自签证书)
  • Cloudflare 账号已绑定支付方式——Cloudflare for SaaS 功能本身免费(每个域名 100 个自定义主机名以内 0 元,仅用于验证身份)
  • 可选:优选 IP 测速工具(CloudflareSpeedTest),或直接用公共优选域名

下文以 example1.com(主域名 / 业务)和 example2.com(辅助域名 / 设施)举例,请替换成你自己的域名,其余照抄。只有一个域名?见下文**《备选:单域名方案》**一章,步骤照做,只是把辅助域名的部分换到同一个域名下。


🚀 主线配置:双域名方案(Halo 博客 + Jitsi Meet 双服务一次配齐)

分工一句话:辅助域名 example2.com 承载全部设施(回退源、优选入口、SaaS 面板),主域名 example1.com 只放用户访问的业务地址。

第 1 步:创建 Tunnel,一次添加三条 Public Hostname

  1. 进入 Cloudflare 控制台 → Zero Trust → Networks → Tunnels
  2. 点击 Create a tunnel,起个名字(如 my-tunnel),复制安装命令到内网机器上执行,确认连接状态为 Connected
  3. 在 Public Hostnames 中依次添加三条规则(Domain 下拉可以选账号里的任何 zone):
SubdomainDomainTypeURLTLS 设置说明
tunnelexample2.comHTTPlocalhost:8090—回退源门牌,指哪个服务都行
blogexample1.comHTTPlocalhost:8090—Halo 博客
meetexample1.comHTTPSlocalhost:8443No TLS VerifyJitsi Meet,见下方 ⚠️

⚠️ URL 只写 IP:端口,不要加 http:// 前缀(协议由"类型"下拉决定)。

⚠️ Jitsi Meet 用的是自签证书:类型选 HTTPS 时必须展开 其他应用程序设置 → TLS,勾选 No TLS Verify(不验证 TLS 证书)——否则 cloudflared 校验源站证书失败,访问一律报 502。Halo 是 HTTP,无此问题;将来有走正式证书的 HTTPS 服务则无需勾选。

⚠️ 回退源门牌(tunnel 那条)只此一条、中性命名——它是所有服务共用的门,不绑定端口;给新服务加的是"业务主机名"规则,不是新的回退源。

💡 门牌指向哪里只影响"直连 tunnel.example2.com 的人看到什么":指向真实服务(如 Halo)可作为分段验证探针——直连能打开 = 隧道段是通的,报错时可以二分定位;指向不存在的端口(如 localhost:80,前提是 cloudflared 所在机器的 80 上没有服务)则直连一律 502,相当于焊死这个公开入口,更安全但会失去上述探针。调试期建议指向真实服务,稳定后随意。

第 2 步:开通 Cloudflare for SaaS,设置回退源

  1. 回到普通控制台,选择辅助域名 example2.com
  2. 左侧 SSL/TLS → Custom Hostnames(自定义主机名)
  3. 如未开通,点击 Enable Cloudflare for SaaS,按提示绑定支付方式(免费,仅验证)
  4. 在 Fallback Origin(回退源) 中填入第 1 步配置的完整主机名:tunnel.example2.com
  5. 点击添加,等待状态变为 Active(有效)

⚠️ 回退源状态卡在"待部署(错误)",提示 DNS records are not setup correctly. Origin should be a proxied A/AAAA/CNAME dns record?

这个报错的意思是:回退源主机名在 DNS 里找不到同名的橙云 A/AAAA/CNAME 记录。逐一检查:

  1. 第 1 步的 tunnel 规则是否已经添加(没有它,DNS 里就没有 tunnel 这条记录);
  2. 辅助域名的 DNS 记录页里 tunnel 那条 CNAME 是否为橙云(灰云会报此错);
  3. 回退源输入框填的是否为完整主机名 tunnel.example2.com,与 DNS 记录一字不差。

核对后点回退源旁的"刷新",验证有几分钟延迟,稍等再刷。

第 3 步:添加自定义主机名(两条业务地址,一次加齐)

  1. 还是在 Custom Hostnames 页面(辅助域名 example2.com 下),点击 Add Custom Hostname
  2. Hostname 填主域名上的业务地址:blog.example1.com,证书验证方式选 TXT
  3. 再来一次,添加 meet.example1.com,同样选 TXT
  4. 到主域名 example1.com 的 DNS 页面添加 TXT 记录。⚠️ 每条主机名页面上列出的每一条 TXT 都要加——每条主机名通常 3 条:2 条 _acme-challenge.*(证书验证,同名不同值)+ 1 条 _cf-custom-hostname.*(主机名预验证)。两条主机名共 6 条 TXT,缺任何一条都过不了
  5. 返回 Custom Hostnames 刷新,等待证书状态变为 Active(有效)(证书签发一般需要几分钟)

⚠️ 主机名状态显示"待定(错误)"或"已移动(错误)",提示 custom hostname does not CNAME to this zone?

意思是 Cloudflare 检测到自定义主机名的解析链没有落在它的边缘上。两种触发:

  1. 业务子域的 DNS 记录还没指向本 zone(比如仍是橙云 → xxx.cfargotunnel.com);
  2. 已指向 ip.example2.com,但 ip 底下写的不是 Cloudflare 官方 IP(如第三方反代 IP)——解析链终点不在 CF 边缘,同样不算"指向本 zone"。

修复:业务子域灰云 CNAME → ip.example2.com,且 ip 底下必须是官方 IP(见进阶二第 4 步),然后点"刷新"。注意证书状态(TXT 签发)不受影响、会保持有效,页面可能照常打开——但主机名不 Active 属于"带病运行",别长期放任。

记住分工:TXT 验证管证书签发,解析链落在本 zone 管主机名生效,两者缺一不可。反过来看,主机名状态变回 Active 也是一道免费的验收信号——它意味着 Cloudflare 亲自确认了你的优选入口指向它的边缘。

完成后,Cloudflare 边缘就"认得" blog.example1.com 和 meet.example1.com 了,且会为它们自动签发覆盖这两个域名的边缘证书。

第 4 步:创建优选入口

在辅助域名 example2.com 的 DNS 页面添加:

类型名称目标代理状态
CNAMEipcf.090227.xyz(公共优选域名,可换)⚪ 灰云

也可以用自己测出的优选 IP:类型选 A,目标直接填 IP 地址。之后要让 cfnb-go 自动接管这条记录的,见下文进阶二。

第 5 步:把业务域名指向优选入口

关键一步。 到主域名 example1.com 的 DNS 页面,处理第 1 步自动生成的两条记录:

名称操作最终形态
blog编辑第 1 步自动生成的记录CNAME → ip.example2.com,⚪ 灰云
meet编辑第 1 步自动生成的记录CNAME → ip.example2.com,⚪ 灰云

改完回自定义主机名页面点刷新,两条主机名的主机名状态应先后变为 Active——至此第 3 步完全闭环。

⚠️ 新建记录时报 An A, AAAA, or CNAME record with that host already exists?

说明这个主机名下已经有一条记录了(同一主机名不允许 CNAME 与 A/AAAA 并存)——通常是第 1 步添加 Public Hostname 时 dashboard 自动生成的橙云 CNAME。不要新建,直接编辑那条已有记录:目标改成 ip.example2.com、代理状态改为灰云、保存。如果列表里那条已经是"灰云 → ip.example2.com",则什么都不用做,记录已正确。

第 6 步:SSL 设置

在辅助域名 example2.com 的 SSL/TLS 页面:

  • 概述 → 右上角「配置」→ 选「自定义 SSL/TLS」→ 加密模式选完全(严格)/ Full (strict)(选"灵活 Flexible"会导致重定向循环,见故障排查;新版界面若显示"自动模式已启用",同样在「配置」里切换为"自定义"后选择)
  • 左侧 边缘证书 页面:开启 始终使用 HTTPS 和 自动 HTTPS 重写

主域名 example1.com 若也托管在 Cloudflare:业务记录是灰云、不经过它的代理层,本方案不依赖主域名的 SSL 设置,保持默认即可。

DNS 记录最终形态(对照检查)

名称所在域名类型指向云朵服务
blogexample1.comCNAMEip.example2.com⚪ 灰Halo :8090
meetexample1.comCNAMEip.example2.com⚪ 灰Jitsi :8443
ipexample2.comCNAME优选域名 / 优选 IP⚪ 灰共用
tunnelexample2.comCNAMExxx.cfargotunnel.com🟠 橙共用门牌

外加:Custom Hostnames 面板(在 example2.com 下)中 blog.example1.com 与 meet.example1.com 的证书状态、主机名状态均为 Active,Fallback Origin 为 tunnel.example2.com 且 Active。

配好后 example1.com 的 DNS 会非常干净:只剩业务 CNAME 和几条验证 TXT,所有"会变的、要折腾的"东西都在 example2.com 上。


🔀 备选:单域名方案

只有一个域名时,把"回退源 + 优选入口 + SaaS 面板"全部放到同一个域名下即可,其余步骤与主线完全一致。与主线只有 5 处差异:

配置项主线(双域名)备选(单域名)
Tunnel 的回退源规则tunnel.example2.comtunnel.example1.com
SaaS 开在哪个域名example2.com(辅助域名)example1.com
回退源地址tunnel.example2.comtunnel.example1.com
优选入口ip.example2.comip.example1.com
自定义主机名的 TXT 验证加在主域名 example1.com 的 DNS加在同一域名的 DNS

怎么选:双域名把"会变的设施"和"业务域名"彻底隔离——主域名换 DNS 服务商、增删业务时,辅助域名上的设施不用动,推荐。单域名少买一个域名、少管一个控制台,适合只有一个域名或追求最简的场景。

从单域名配置切换到双域名?(清理清单)

  1. Zero Trust 里把原来的回退源规则 Domain 改为辅助域名(或删除重建),让它生成 tunnel.example2.com
  2. 回退源改为 tunnel.example2.com(在辅助域名的 Custom Hostnames 页面重新填写)
  3. 删除主域名 DNS 里旧的 tunnel 记录(Tunnel 自动生成的橙云 CNAME)和 ip 记录——优选设施已整体搬到辅助域名
  4. 主域名各业务子域的 CNAME 改指 ip.example2.com(辅助域名下的优选入口)

🧩 进阶一:再添加更多服务

第 4 个、第 5 个服务?照葫芦画瓢,每个服务四步(以新的"服务三"为例):

  1. Tunnel 加一条业务规则:Public Hostname 添加,Subdomain 起名、Domain 选 example1.com、类型和 URL 填服务的协议端口(HTTPS 自签服务记得勾 No TLS Verify);
  2. SaaS 加一条自定义主机名:服务三.example1.com,TXT 验证的 3 条 TXT 加到 example1.com 的 DNS;
  3. 主域名 DNS:把第 1 步自动生成的同名橙云记录编辑成灰云 CNAME → ip.example2.com;
  4. 回自定义主机名页刷新,等两个状态 Active。

回退源和优选入口都不动——它们是全局设施。回退源是 zone 级设置、只能填一个;优选入口也是全体服务共用一条。

💡 也可以不拆子域、用路径区分(如 blog.example1.com/ha),但需要手写 config.yaml 的 path 规则,且不少内网服务假设自己部署在根路径,容易踩坑——推荐用子域。


🤖 进阶二:配合 cfnb-go 全自动优选

优选 IP 会随时间失效(见常见问题 #5),手动更换很麻烦。可以让 cfnb-go 接管 ip 这条记录:它定期从多个数据源聚合 Cloudflare IP,经 TCP 延迟、HTTP 延迟/抖动、真实带宽多重测速后,把最优的一批 IP 自动批量写入 Cloudflare DNS。这也正是"优选入口"单独用一个子域的价值——工具只碰 ip,业务记录永远不用动。

对接步骤

  1. 创建 API Token:Cloudflare 右上角头像 → My Profile → API Tokens → Create Token → 模板选 Edit zone DNS,Zone Resources 选择辅助域名(优选入口所在域名)→ 创建并复制 Token
  2. 记下 Zone ID:辅助域名概览页右下角
  3. 修改 cfnb-go 的 config.json 关键字段:
字段值说明
CF_ENABLEDtrue开启 DNS 自动更新
CF_API_TOKEN上一步的 Token只需 Zone → DNS → Edit 权限
CF_ZONE_ID辅助域名的 Zone ID优选入口所在的那个 zone
CF_DNS_RECORD_NAMEip.example2.com被接管的优选入口(完整主机名)
DNS_RECORD_TYPEA⚠️ 默认是 TXT,必须改成 A
DNS_UPDATE_TARGET_COUNT15每轮写入 15 条 A 记录,解析时轮询分发
CF_PROXIEDfalse⚠️ 保持默认——优选入口必须灰云
  1. 先收紧数据源,只留 Cloudflare 官方 IP 源:默认 ADDITIONAL_SOURCES 里的第三方反代源(ipdb.api.030101.xyz?type=bestproxy)和聚合大列表(zip.cm.edu.kg 等)混有大量非 Cloudflare 的第三方服务器 IP——实测会写入 Azure/DigitalOcean 之类的机器,测速通过但业务域名直接 ERR_CONNECTION_TIMED_OUT。本方案的优选目标必须是 Cloudflare 官方 anycast IP(边缘节点才认 SNI)。推荐只保留这三个(解析出的都是官方 IP),其余 enabled: false:

    "ADDITIONAL_SOURCES": [
        { "url": "cf.090227.xyz", "enabled": true },
        { "url": "cf.877774.xyz", "enabled": true },
        { "url": "https://ghproxy.net/https://raw.githubusercontent.com/yuanxiawan/cfipv4db/refs/heads/main/high_score_ips.txt", "enabled": true }
    ]
    
  2. 删掉手动建的 ip CNAME(就是第 4 步建的那条:辅助域名 DNS 里 ip → 优选域名):cfnb-go 只按 A 类型清理旧记录,残留的 CNAME 会和新 A 记录冲突,导致写入失败。tunnel(回退源)和主域名里的业务 CNAME 都不用动。删完后 ip 会短暂无解析、业务域名暂时打不开——立即手动跑一轮 cfnb-go(docker compose -f deploy/docker-compose.yml run --rm cfnb)把空窗缩到最短。
    ⚠️ 删了 CNAME 又不运行 cfnb-go ≠ 回退到不优选,而是整条解析链断裂:业务子域的 CNAME 指向 ip 这个中间层,它底下没有记录时 blog / meet 直接解析失败、完全打不开。想临时放弃优选,正确做法是把业务记录编辑回橙云 → xxx.cfargotunnel.com(恢复 Tunnel 默认入口),而不是留空中层。

  3. 验证:dig ip.example2.com +short 应返回一批 Cloudflare 官方 IP(104.x 等段,而不是 Azure/DigitalOcean 之类的第三方机器);DNS 页面 ip 名下出现一批灰云 A 记录

之后每一轮:cfnb-go 测速 → 原子替换 ip 的全部 A 记录 → 所有指向 ip 的业务域名自动用上最新优选,全程无需人工干预。

💡 多地部署(如公司、家里各一台设备各一条隧道):两台机器同时跑 cfnb-go、各写各的优选入口记录即可,互不干扰——完整拓扑与注意事项见 cfnb-go README《多实例部署:多台机器 / 多个域名同时优选》。本站的完整落地步骤(拓扑选型、双隧道域名规划、Docker 速查)见下文**《🏢 进阶三:两地部署》**。

优选 IP 不可达:业务域名报 ERR_CONNECTION_TIMED_OUT

自动优选后浏览器报"响应时间过长 / 连接超时"(不是 DNS 错误,也不是 404/502)= 解析成功但选出的 IP 在你的网络下连不上——通常是 cfnb-go 的测速环境与浏览器环境不一致,或选入了不可靠的第三方反代 IP。三步定位:

# 1. 看 cfnb-go 写入了哪些 IP(Windows 用 nslookup)
nslookup ip.example2.com

# 2. 逐个测 443 端口可达性
# PowerShell:Test-NetConnection <IP> -Port 443(TcpTestSucceeded : True 即通)
# cmd 用自带的 curl(出现 TLS/证书相关输出 = TCP 通;Timed out = 不通):
# curl -sv --connect-timeout 5 https://<IP>:443
Test-NetConnection <上面拿到的某个IP> -Port 443

# 3. 对照公共优选域名(它可达 → 问题在 cfnb-go 选出的 IP)
Test-NetConnection cf.090227.xyz -Port 443

修复与恢复:

  • config.json 里把第三方反代源(如 ipdb.api.030101.xyz?type=bestproxy)enabled: false,只留 Cloudflare 官方 IP 源,重新跑一轮;
  • 确认 cfnb-go 的运行网络与浏览器走同一出口(同一宽带/运营商),测出的结果才对你有效;
  • 恢复访问的临时办法:把业务子域记录改回橙云 → xxx.cfargotunnel.com(默认入口,慢但能访问),修好后再改回灰云 → ip.example2.com。

⚠️ 注意:TCP 可达 ≠ 可用。 实测踩坑:第三方反代 IP 的 TCP 443 可能全部可达(TcpTestSucceeded: True),但浏览器照样 ERR_CONNECTION_TIMED_OUT——反代机接受了 TCP 连接,却对自定义域名的 SNI 静默丢弃(它们面向的是代理工具场景,转发的是 Cloudflare 官方域名的 SNI)。决定性验证(模拟浏览器的 TLS 行为,PowerShell 用 curl.exe):

# 经第三方 IP 访问你的域名——预期 TLS 挂起/超时
curl.exe -sv --connect-timeout 8 --resolve meet.example1.com:443:<第三方IP> https://meet.example1.com

# 经 Cloudflare 官方 IP 访问你的域名——预期握手成功、返回页面
curl.exe -sv --connect-timeout 8 --resolve meet.example1.com:443:<官方IP> https://meet.example1.com

⚠️ 变体症状:页面能打开、应用报"Connection error"(如 Jitsi Meet 的黄色警告页)。多连接应用(页面 + WebSocket + XMPP 信令)在 DNS 轮询下会命中不同的优选 IP——页面命中的活着、WebSocket 命中的死了,于是"页面正常、信令失败"。橙云(全部连接走官方边缘)正常、灰云报错,即为此症。根治方法相同:优选入口只写 Cloudflare 官方 IP。

这也是测速工具在此场景的盲区:HTTP 检测用的 SNI 是 Cloudflare 官方域名,反代会转发;而浏览器用的 SNI 是你的自定义域名,行为不可控。优选目标必须是 Cloudflare 官方 anycast IP——SNI 路由、证书、回退源整套机制只存在于官方边缘。

❓ 常见疑问:第三方反代 IP 有可能比官方 IP 更快,为什么不用?

有可能——优质线路 VPS(CN2 GIA / CMI)反代到 Cloudflare 快速 PoP,端到端确实可能超过 Anycast 给你分配的默认路径(官方 anycast 路由到哪个 PoP 由 BGP 决定,你无法选择)。但对对外提供服务的域名,速度换不来这四条代价:① SNI 行为不可控(实测 TCP 通但浏览器超时);② 寿命只有分钟~天级(官方 anycast 是周月级);③ L7 反代能看到明文流量;④ Cloudflare 主机名校验不通过,证书续期与路由保障失去官方承诺。

场景区分:代理工具自己用(挂了换一个就行)→ 第三方反代合理;对外服务的域名(7×24、多用户)→ 只用官方 IP。另外官方 IP 段内部本身就有优选空间(不同官方 IP 路由到不同 PoP,cfipv4db 扫的就是这个),官方段内优选已能拿到大部分收益。

另一个常见分支:本地路由器 / 自定义 DNS 报 Server failed(SERVFAIL)——如 OpenWrt/ImmortalWrt、AdGuard Home、SmartDNS 等环境。用公共 DNS 对比:

nslookup ip.example2.com 223.5.5.5
  • 公共 DNS 正常、路由器 SERVFAIL → 本地 DNS 的问题。特别注意:若写入的记录里混入了私有地址(数据源脏数据),开启了 rebind 保护的 dnsmasq 会拒绝整个响应——cfnb-go 换用纯 Cloudflare 官方 IP 源重跑即可根治;
  • 公共 DNS 也查不到 → 去辅助域名的 DNS 页面确认 ip 名下是否真的写入了 A 记录(cfnb-go 日志应有"已将 ip.example2.com 指向 N 个 IP"),没有则是它的 API 配置问题(Token 权限 / Zone ID / 记录名)。

还有一层容易漏掉:浏览器自带的"安全 DNS"(DoH)会绕过系统 DNS——Chrome/Edge 开启后走自己的 DoH 端点,nslookup(走系统 DNS)与浏览器的解析结果可能完全不同,尤其是来源不明的自定义 DoH 端点,可能存在分流 / 假 IP(fake-ip)行为,导致"直连 IP 测试全通、浏览器却超时"的矛盾现象。若两者表现不一致,先在浏览器设置里关闭安全 DNS(或换成 https://dns.alidns.com/dns-query 等公共 DoH),到 chrome://net-internals/#dns 清除主机缓存后对比测试,最终让浏览器解析与系统解析收敛到同一个可信上游。

🛠 如果坚持使用第三方反代源(不换官方 IP),把运行姿态调整为"接受故障、快速恢复":

  • 主机名状态会持续显示"已移动(错误)"——功能可用但属带病运行,心里有数即可;
  • RUN_INTERVAL 保持短周期(默认 5 分钟级),死掉的 IP 最快在下一轮被原子替换;
  • DNS_UPDATE_TARGET_COUNT 保持较大(如 15),多 IP 轮询分摊"命中死 IP"的概率;
  • 开启 WxPusher 通知,筛选/DNS 异常第一时间知道;
  • 盯证书到期日:主机名不 Active 时证书可能不自动续期,到期前一个月回到 Custom Hostnames 页面确认证书状态,必要时删除重新添加、走一次 TXT 验证;
  • 多连接应用(Jitsi)会偶发 "Connection error"——这是第三方源的固有代价,出现时手动重跑一轮 cfnb-go 即可临时恢复。

更新频率怎么选

运行间隔由 RUN_INTERVAL 控制(Docker 部署在 deploy/.env 里改,默认 300 秒):

场景建议
网页 / API 加速(Halo 博客)15 ~ 30 分钟
实时音视频(Jitsi Meet / WebRTC 类)12 ~ 24 小时,或固定一批 IP——媒体流走 UDP 直连不经 Cloudflare,优选只影响网页与信令;频繁切 IP 会中断进行中的会话

💡 两个服务共用一个 ip 入口时取折中:30 分钟 ~ 2 小时皆可;若 Jitsi 会议频繁,倾向更长的间隔。


🏢 进阶三:两地部署 —— 公司 + 家里两台设备

一句话总结:两台设备都跑 cloudflared ≠ 双倍保险,先分清两台机器上的服务是什么关系。同一套服务互为备份 → 保持一条隧道、两台各挂一个连接器(官方高可用形态);服务只在一台上 → 停掉多余那台的连接器;两地各跑各的服务 → 必须拆成两条隧道、两套设施层。最常见的错误形态是"同一条隧道挂两台、服务只在一台上":请求随机派发,约一半概率落到没跑服务的那台,业务域名时好时坏。

先看懂:隧道规则不区分连接器

Tunnel 的 Public Hostname 规则是整条隧道级别的,不跟某台机器绑定。一条隧道挂两个连接器时,边缘会把每个请求随机派给任一健康的连接器,再由它在自己那台机器上按规则找 localhost:端口:

flowchart TD
    U["👤 访问 blog.example1.com"] --> CF["🌐 边缘 → SaaS 回退源 → 隧道"]
    CF -->|"随机派发 ①"| C1["🏢 公司连接器:本机 8090 有 Halo → 200"]
    CF -->|"随机派发 ②"| C2["🏠 家里连接器:本机没跑 Halo → 502"]
  • 服务只在一台上 → 约一半请求 502,典型症状是时好时坏、刷新一下又好了;
  • 两台都跑同一服务 → 高可用成立,但数据一致性要自己解决——Tunnel 只帮你切流量,不帮你同步数据。

查一下自己现在是哪种:Zero Trust → Networks → Tunnels → 点开隧道名 → Connectors。列出 2 个连接器 = 两台连的是同一条隧道。

先对号入座:三种情况

你的实际情况正确做法看哪节
两台跑同一套服务,想互为备份保持一条隧道 + 双连接器(这就是官方 HA)情况 A
服务只在一台上(比如只在家里),另一台当时顺手也接上了停掉多余连接器;或给它单建隧道跑它自己的服务情况 C
两台跑不同的服务,都要对外暴露拆两条隧道,第二个站点补一套设施层情况 B
其实想要的是两地内网互访(不对外暴露)不走本方案:Private Network + WARP,见章末提示章末

情况 A:同一套服务,两地互为备份(一条隧道 + 双连接器)

这是唯一"两台连同一条隧道"是对的场景,主线配置一字不改:一条隧道、一个回退源、一个优选入口、同一批自定义主机名。两台设备用同一个 token 各起一个 cloudflared 容器即可。要成立,必须满足:

  1. 两台的服务和端口完全一致——规则说 blog → localhost:8090,两台机器的 8090 上都得真有服务,缺谁的谁就随机 502;
  2. 数据一致性自理——Halo 这类有状态服务两台各存一份 = 内容漂移:要么共享同一个数据库/存储,要么接受"一台为主、另一台冷备";
  3. cfnb-go 只在一台跑——两台都写同一条 ip 记录会互相覆盖、来回翻转(两边网络测出的最优 IP 本就不同),等于自己把自己的 DNS 折腾坏。

验证:docker stop cloudflared 停掉一台,业务域名应只中断几秒(另一台接住全部流量)。这就是双连接器买到的东西——连接器级冗余,顺便让 cloudflared 升级可以一台一台来、全程不断流。

加分项:按"访问者"拆主机名,两地各自优选

优选 IP 取决于访问者所在的网络,跟服务器在哪无关——一条 ip 记录同时服务"在公司访问"和"在家里访问",一边测出的优选对另一边未必最优。解法是再注册一对业务主机名、按"人在哪"分流,仍然只要两个域名:两条都是 example2.com 下的自定义主机名(TXT 照加、隧道里各加一条业务规则,都指向同一个服务),都落回同一条隧道:

名字CNAME 指向由谁优选
blog-home.example1.comip-home.example2.com家里机器的 cfnb-go
blog-office.example1.comip-office.example2.com公司机器的 cfnb-go

ip-home / ip-office 是两条不同名的记录,两台 cfnb-go 各写各的、互不干扰;人在公司就用 blog-office,在家就用 blog-home。代价:主机名和 TXT 翻倍,且"同一服务两个网址"的心智负担略增——非重度两地使用可以不做,保持单主机名 + 单 ip 的折中即可。

情况 C:服务只在一台上 —— 停掉多余连接器

最常见也最容易被忽略:服务只在家里,公司那台"顺手"也接进了同一条隧道。此时的公司连接器是纯粹的随机 502 源,不提供任何冗余价值——直接在公司的设备上 docker compose down(或 docker rm -f cloudflared)停掉它,业务域名立刻 100% 稳定。哪天公司设备上真有服务要暴露,再按情况 B 单建一条隧道。

公司设备停掉连接器后,若还想让"人在公司时访问家里服务更快",用情况 A 的加分项即可——源站仍是家里那条隧道,只是给公司网络单独优选一条 ip-office。

情况 B:两地各跑各的服务(两条隧道 + 两套设施层)

✅ 已经用不同 token 建了两条隧道(公司、家里各一条)? 拓扑已经对了——主线第 1 步里"建隧道"这半步天然完成。自检:Tunnels 列表应有两条隧道、Connectors 各 1 个;若哪条隧道下显示 2 个连接器,说明 token 用重了,那台要改连自己的隧道。接下来的工作:让第二条隧道的流量有自己独立的"门"——要么补一套它自己的设施层(回退源 + SaaS 面板 + 优选入口,需第三个域名),要么用 Custom Origins 在现有辅助域名里开一扇门(两个域名就够,见下文《只有两个域名?》),小心下面的回退源约束。

为什么一条隧道装不下

两个硬约束:

  1. 隧道规则不区分连接器(本进阶开头讲过)——oa.example1.com → localhost:8080 这条规则两台机器都会执行,谁家没有这个服务谁就 502;
  2. 回退源是 zone 级设置、一个辅助域名只能填一个(进阶一末尾也提过)——它决定该域名下所有自定义主机名的流量最终回哪条隧道。公司服务的流量进了家里的隧道,家里可没有公司那台机器。

所以:一条隧道服务一个站点——公司站点的流量必须能送进公司那条隧道:要么给它第二个辅助域名(回退源各指各的,见上面的域名规划),要么用 SaaS 的 Custom Origins(自定义源) 在同一个辅助域名里给它单独"开一扇门"(见下文《只有两个域名?》)。

⚠️ 踩中约束的症状:如果第二个站点的业务主机名注册后走的是默认回退源(没配自定义源、也没换辅助域名),它的流量会稳定地涌进回退源指向的那条隧道——表现为第二个站点的业务域名恒定 404/502(不是时好时坏:流量一直进错隧道,而那条隧道里没有它的 Host 规则)。修法:改注册到第二个辅助域名的 SaaS 下(回退源各指各的隧道),或给该主机名配 Custom Origins 自定义源。

域名规划(3 个域名;只有两个域名也能做,见下文《只有两个域名?》)

站点业务域名(都在 example1.com)辅助域名(设施层)回退源优选入口
🏠 家blog / meetexample2.comtunnel.example2.com → 隧道①ip.example2.com
🏢 公司oa(示例)example3.com(新购,托管到同一个 Cloudflare 账号)tunnel.example3.com → 隧道②ip-office.example3.com
flowchart TD
    B1["👤 blog / meet.example1.com"] -->|"CNAME → ip.example2.com"| E1["🌐 边缘(example2.com 的 SaaS)<br>回退源 tunnel.example2.com"]
    E1 --> T1["🏠 隧道①:家里 cloudflared<br>Halo :8090 / Jitsi :8443"]
    B2["👤 oa.example1.com"] -->|"CNAME → ip-office.example3.com"| E2["🌐 边缘(example3.com 的 SaaS)<br>回退源 tunnel.example3.com"]
    E2 --> T2["🏢 隧道②:公司 cloudflared<br>公司要暴露的服务"]

配置步骤:主线第 1~6 步 × 2

两个站点各自完整走一遍主线第 1~6 步,只是名字按下表替换:

主线步骤站点① 家里站点② 公司
第 1 步 隧道my-tunnel-home,规则:tunnel.example2.com + 家里各业务主机名my-tunnel-office,规则:tunnel.example3.com + 公司各业务主机名
第 2 步 回退源tunnel.example2.com(example2.com 的 SaaS 面板)tunnel.example3.com(example3.com 的 SaaS 面板)
第 3 步 自定义主机名blog / meet.example1.com,TXT 加在 example1.comoa.example1.com 等,TXT 也加在 example1.com
第 4 步 优选入口ip.example2.comip-office.example3.com
第 5 步 业务 CNAME→ ip.example2.com→ ip-office.example3.com
第 6 步 SSL 设置example2.com:完全(严格)example3.com:完全(严格)

易错点:

  • ⚠️ 两条隧道绝不共用 token——同 token = 同一条隧道,又绕回"随机派发"的老问题。第二条隧道在 Zero Trust 里单独 Create a tunnel,用它自己的 token 起公司那台的容器。
  • ⚠️ TXT 验证记录全部加在主域名 example1.com 的 DNS 里,两个站点加起来十几条,命名/备注区分清楚,千万别互删。
  • ⚠️ 两个 SaaS 面板分别位于 example2.com 和 example3.com 的 SSL/TLS → Custom Hostnames 下,加自定义主机名时先看一眼当前 zone,别加错面板。
  • example3.com 必须添加到同一个 Cloudflare 账号——第 1 步隧道规则的 Domain 下拉里要能选到它。

cfnb-go 两地各跑一份

即进阶二末尾"多地部署"提示的完整版:两台机器各跑一份 cfnb-go、各写各的优选入口记录,互不干扰。两份配置只有这几处不同:

配置项家里那份公司那份(3 域名方案)公司那份(2 域名方案)
CF_API_TOKEN只授权所在 zone 的 DNS 编辑只授权 example3.com同一个 example2.com 的 Token 即可
CF_ZONE_IDexample2.com 的 Zone IDexample3.com 的 Zone ID同一个 example2.com 的 Zone ID
CF_DNS_RECORD_NAMEip.example2.comip-office.example3.comip-office.example2.com
  • 优选必须在各自网络下测:公司宽带和家庭宽带的最优 IP 基本不同,把家里测出的结果写进 ip-office 是无效的——好在两份实例天然就在各自网络里跑,这正是"各写各的"的价值;
  • 公司网络若禁 UDP(QUIC 走 7844),cloudflared 会自动回落到 http2/443,也可显式指定(见下方 Docker 速查)。

只有两个域名?用 Custom Origins 给第二条隧道"开一扇门"(推荐)

Custom Origins(自定义源)是 Cloudflare for SaaS 的官方功能:给个别自定义主机名指定"不走默认回退源、改走另一个源服务器",全部套餐可用(含 Free)。于是一个辅助域名就能同时服务两条隧道——默认回退源进家里隧道,公司主机名单独指定进公司隧道:

flowchart TD
    B1["👤 blog / meet.example1.com"] --> E1["🌐 边缘:SNI 匹配自定义主机名<br>未配自定义源 → 默认回退源 tunnel.example2.com"]
    E1 --> T1["🏠 隧道①(家里)"]
    B2["👤 oa.example1.com"] --> E2["🌐 边缘:SNI 匹配自定义主机名<br>配了自定义源 → tunnel-office.example2.com"]
    E2 --> T2["🏢 隧道②(公司)"]

在主线第 1~6 步的基础上,只多 3 个动作:

  1. 公司隧道加门牌:隧道②的 Public Hostname 加一条 tunnel-office + example2.com(Type/URL 随意,比如 HTTP → localhost:80)——dashboard 自动生成橙云 CNAME,指向隧道②的 cfargotunnel.com,这就是公司主机名的自定义源;
  2. 公司业务主机名配自定义源:在 example2.com 的 Custom Hostnames 面板添加 oa.example1.com 时,在**源服务器(Origin Server)**处选择自定义源 → tunnel-office.example2.com(API 创建则带 "custom_origin_server" 参数);TXT 照旧加在 example1.com;
  3. 优选入口分家:example2.com 的 DNS 新增一条灰云记录 ip-office(家里现有的 ip 不动),公司 cfnb-go 只写 ip-office——同一台 cfnb-go 配置里只有记录名不同。

DNS 最终形态(对照检查):

名称所在域名类型指向云朵说明
blog / meetexample1.comCNAMEip.example2.com⚪ 灰家里业务,主线原样不动
oaexample1.comCNAMEip-office.example2.com⚪ 灰公司业务
ipexample2.comA家里 cfnb-go 写入的优选⚪ 灰家里 cfnb-go 接管
ip-officeexample2.comA公司 cfnb-go 写入的优选⚪ 灰公司 cfnb-go 接管
tunnelexample2.comCNAME隧道①的 cfargotunnel.com🟠 橙默认回退源
tunnel-officeexample2.comCNAME隧道②的 cfargotunnel.com🟠 橙公司主机名的自定义源

注意:

  • ⚠️ 自定义源必须是同账号里一条橙云 A/AAAA/CNAME 记录(不能直接填 IP、不支持通配符)——第 1 步的门牌正好满足;
  • ⚠️ 添加公司主机名时忘了选自定义源 = 它走默认回退源进家里隧道,恒定 404——回面板补选即可,证书与 TXT 都不用重做;
  • 隧道回源不经过传统 TLS 回源,官方文档里 Full (strict) / SNI rewrite 的注意事项与隧道场景无关;
  • 家里的 blog/meet 什么都不用改——已 Active 的自定义主机名、已签发的证书全部保持原样。

其他替代:公司业务走橙云经典(example1.com 托管在 Cloudflare 时,公司子域橙云 CNAME → 隧道②的 cfargotunnel.com)——零新概念但入口 IP 随机无优选,主域名在第三方 DNS 则此路不通。第三个域名始终是"最对照主线"的选项,想要业务/设施彻底隔离时随时可加。

Docker 部署速查(两台通用)

# docker-compose.yml —— 每台设备一份
# 情况 A(一条隧道双连接器):两台填同一个 TUNNEL_TOKEN
# 情况 B(两条隧道):两台各填各的 token,绝不能相同
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    network_mode: host          # Linux(NAS/小主机)推荐:容器里的 localhost 就是宿主机
    # 用不了 host 模式(Windows/macOS/部分 NAS)就删掉上一行,并做两件事:
    #   1) Public Hostname 的 URL 里 localhost 全改成 host.docker.internal
    #   2) 加一行:  extra_hosts: ["host.docker.internal:host-gateway"]
    environment:
      - TUNNEL_TOKEN=<你的TUNNEL_TOKEN>          # Zero Trust → 该隧道 → Install connector 页可复制
      - TUNNEL_TRANSPORT_PROTOCOL=http2          # 可选:公司网络禁 UDP/QUIC 时强制走 443,其余场景删掉

升级照旧 docker compose pull && docker compose up -d;双连接器(情况 A)时一台一台升——先升一台、状态 Connected 且业务正常后再升另一台,全程不断流。

公司设备注意事项

  • ⚠️ 先确认公司允许:在公司设备上对外暴露服务前想清楚合规与审计风险;只放个人服务,绝不把公司内网网段加进 Private Network 路由;
  • 公司防火墙可能拦 cloudflared 的出站(QUIC/UDP 7844、443)——拦了就用 TUNNEL_TRANSPORT_PROTOCOL=http2 走 443;
  • 公司宽带出口多变(NAT/代理),ip-office 的优选结果以"你人在公司时实际访问"为准,失效就重跑一轮。

❓ 想要的其实是"公司内网 ⇄ 家里内网"互相访问(比如在家连公司 NAS、在公司连家里下载机),而不是把服务暴露到公网?那是另一套玩法:Tunnel 的 Private Network 路由 + WARP 客户端,按网段路由、不经 SaaS 优选链路,参见 Cloudflare 官方文档。

💡 记忆口诀:同服务 → 一条隧道多连接器;不同服务 → 一条隧道一个站点;优选看的是访问者的网络,不是服务器的。


✅ 验证

1. 浏览器直测

  • 访问 https://blog.example1.com,应看到 Halo 博客;
  • 访问 https://meet.example1.com,应看到 Jitsi Meet。

可按 F12 → Network 查看证书,确认覆盖对应业务域名。

2. DNS 解析检查

# 业务域名应先指向优选入口
dig blog.example1.com +short
dig meet.example1.com +short
# 预期输出:ip.example2.com.(单域名方案则为 ip.example1.com.)
# 再往下解析就是优选域名或优选 IP

# 回退源应指向隧道
dig tunnel.example2.com +short
# 预期输出:xxx.cfargotunnel.com.
# (单域名方案:改查 tunnel.example1.com)

3. 分段验证

  • 直连回退源 https://tunnel.example2.com(单域名方案为 https://tunnel.example1.com)——应看到 Halo 博客(第 1 步门牌规则指向的服务),说明"隧道 + 回退源"这段是通的;
  • 再访问 https://blog.example1.com 和 https://meet.example1.com——都能打开且速度提升,说明"优选 + SNI 匹配 + Host 分流"整条链路是通的。

⚠️ 常见问题排查

1. ERR_TOO_MANY_REDIRECTS

  • 原因:SSL/TLS 加密模式设为"灵活 (Flexible)",与源站强制 HTTPS 循环跳转。
  • 解决:加密模式改为 完全 (Full) 或 完全(严格),Tunnel 的 Public Hostname 类型按服务实际协议选。

2. 502 Bad Gateway

  • 可能原因:cloudflared 未运行、内网服务未启动、Public Hostname 的 URL/类型填错、HTTPS 服务是自签证书但没勾 No TLS Verify、自定义主机名未到 Active。
  • 排查:先直连回退源(双域名为 https://tunnel.example2.com,单域名为 https://tunnel.example1.com)。通了说明隧道没问题,问题在自定义主机名;不通则检查 cloudflared 进程、URL 配置和 No TLS Verify。

3. 404(页面显示 Not Found)

  • 原因:自定义主机名的流量进了隧道,但 Tunnel 里没有与该业务主机名同名的 Public Hostname 规则,落到 catch-all 404。
  • 解决:回退源规则不参与分流——每个业务主机名都要有一条同名的 Public Hostname(如 meet + example1.com),URL 指向对应服务端口。

4. 证书报错 / 证书不覆盖业务域名

  • 原因:自定义主机名未生效,TXT 验证未完成。
  • 解决:到 Custom Hostnames 页面检查状态,按提示补齐 TXT 记录后等待 Active。

5. 用了一段时间又变慢了

  • 原因:优选 IP / 优选域名会随时间失效。
  • 解决:定期更新 ip 记录指向的优选域名或 IP;可用 CloudflareSpeedTest 测速,或用 cfnb-go 全自动优选并更新 DNS(见进阶二)。

6. 出现 Error 1014: Cross-User Banned 或 CNAME 循环

  • 原因:业务子域或 ip 记录开了橙云,代理层互相嵌套。
  • 解决:检查这些记录必须是灰云(见上文"为什么业务域名和 ip 必须是灰云")。

7. Jitsi Meet:页面能开但报 "Connection error",或进了会议音视频不通

这是两个不同层的问题,别混为一谈:

  • "Connection error"(黄色三角全屏页)是信令层(XMPP/WebSocket)失败,发生在进会议室之前或会议掉线时。排查方向是本文的全部内容:优选 IP 是否官方、WebSocket 是否经稳定路径到达隧道。
  • "进了会议室但音视频不通/会议崩溃"是媒体层:浏览器与 JVB 之间的音视频走 UDP 10000,Cloudflare Tunnel 不支持 UDP——这与优选无关,橙云灰云都一样。单人测试不会暴露此问题,双人音视频测试才会。
  • 媒体层解法:启用 JVB 的 colibri-ws(媒体改走 WebSocket 经 Tunnel 中继,音视频可用但有性能损耗),或部署 TURN/TLS,或接受"外部访问仅信令、局域网内音视频正常"的局限。

📚 参考与致谢

1

评论区