技术笔记Mon Jun 22 2026 00:00:00 GMT+0000 (Coordinated Universal Time)15 分钟阅读

我的 Cloudflare Worker 在国内访问不了的那些天,以及最终解决的全过程

当个人云盘只能让国外朋友用、而我自己打不开时,我以为是 GFW 的诅咒。折腾了一整天后我发现:换个域名就够了。

Z
ZhangKing
作者
我的 Cloudflare Worker 在国内访问不了的那些天,以及最终解决的全过程

我的 Cloudflare Worker 在国内访问不了的那些天

以及最终解决的全过程


上个月我写过一篇文章叫《死在了网络上》。 说的是我好不容易把个人云盘搭好,结果自己却打不开。 国外朋友测试一切正常,我这边一直 Connection reset。 我以为这件事就到此为止了。

但这几天我又折腾了一整天,终于解决了。 这篇文章,我想完整记录下: 问题是什么 → 我试了什么 → 最终怎么解决的 → 整个过程

重点不是技术细节,重点是"国内访问"这条主线

⚠️ 隐私说明:本文所有地址、域名、Worker 名、目录名都用占位符代替,避免被 GFW 溯源后封杀。请根据自己实际情况替换。


第一章:问题的样子

部署明明成功了

我部署了一个基于 Cloudflare Worker 的个人云盘:

$ wrangler deploy
Uploaded <your-worker-name> (7.92 sec)
Published <your-worker-name> (7.92 sec)
   https://<your-worker-name>.<your-account>.workers.dev

部署成功,Worker URL 显示正常,curl 测试返回 200。

理论上,我现在应该有一个属于自己的网盘了。

但是我打不开

我用浏览器打开那个 URL:

等待中...
等待中...
等待中...
ERR_CONNECTION_RESET

打不开

我用手机 4G 测试:

等待中...
等待中...
等待中...
无法访问此网站

还是打不开

我让一个国外朋友测试:

"完美!能打开,能登录,能上传!"

国外能访问,国内不行

这就是问题

国内访问 Cloudflare *.workers.dev 子域,被 GFW 精准干扰

不是 Cloudflare 服务挂了,不是 Worker 代码错了,不是 DNS 解析有问题。 就是 GFW 把 workers.dev 这个域名精准屏蔽了

这就是问题的样子:

  • ✅ 国外:完全正常
  • ❌ 国内:完全打不开

一个只能让国外用户用的网盘,对自己来说有什么意义?


第二章:我的第一反应——认命

写《死在了网络上》

那篇文章里,我接受了现实:

"墙内用户用 VPN/4G 才能用" "死在了网络上,不是死在技术上"

写完后,这件事就这么搁置了。

毕竟:

  • 代码已经写好了
  • 服务已经跑起来了
  • 国外朋友能用
  • 我自己凑合着用 4G

我以为这就是终点了

但有个念头一直在

但心里一直有个声音:

"如果不用 workers.dev 子域呢?" "如果用我自己的域名呢?" "GFW 屏蔽的是 workers.dev,还是 Cloudflare 整个生态?"

这才是这篇文章的核心问题


第三章:真正的诊断

先问对问题

我之前一直在问"为什么访问不了",但没问对问题。

真正应该问的是

"GFW 屏蔽的是什么?"workers.dev 这个域名? 还是 Cloudflare 整个 CDN 网络? 还是 Cloudflare 的某些 IP 段?

答案决定了解决方案。

三个层次的诊断

诊断 1:Cloudflare CDN 是否在国内被墙?

我先访问了一些 Cloudflare 的其他服务:

  • ✅ Cloudflare 控制台:国内能访问
  • ✅ Cloudflare R2 公开 URL:国内能访问
  • *.workers.dev 子域:国内被墙

结论

Cloudflare 整体服务在国内能用,只有 workers.dev 子域被精准干扰

诊断 2:是 DNS 被污染,还是 IP 被屏蔽?

我用 dig 测试:

$ dig <your-worker-name>.<your-account>.workers.dev A +short
# 返回 Cloudflare 的 IP

$ dig <your-worker-name>.<your-account>.workers.dev AAAA +short
# 返回 Cloudflare 的 IPv6

DNS 解析正常。问题不在 DNS。

结论

DNS 没被污染,是 TCP 连接的 IP 被屏蔽

诊断 3:如果是 IP 被屏蔽,换个 IP 行不行?

关键思路

workers.dev 子域被墙 → 我用自己的域名行不行? 自己的域名指向 Cloudflare CDN → IP 段不同 → 可能不被屏蔽

这就是我接下来要验证的核心假设。


第四章:解决方案的思路

关键发现

我意识到一件事:

GFW 屏蔽的是 *.workers.dev 这个域名,不是 Cloudflare 整个 CDN。

如果我用自己的域名,解析到 Cloudflare 的 CDN,理论上:

浏览器 → DNS(我的域名) → Cloudflare CDN(不同 IP 段)→ Worker
              ↑                    ↑
          不在 GFW 黑名单      不在 GFW 黑名单

这样就能绕过 GFW!

这个思路对吗?

需要验证。

验证方法

  1. 注册一个自己的域名(指向 Cloudflare 名称服务器)
  2. 把域名解析到 Cloudflare
  3. 把 Worker 绑定到这个自定义域名
  4. 测试国内访问

如果假设成立,国内就能访问了。

我开始实施

接下来的问题:

  1. 怎么把 Worker 绑定到自定义域名?
  2. DNS 怎么配置?
  3. SSL 证书怎么申请?

Cloudflare 提供两种方式:

| 方式 | 优点 | 缺点 | |------|------|------| | Workers Routes | 灵活 | 需要手动配置 DNS | | Custom Domains | 自动管理 DNS | 要求域名"干净" |

我选 Custom Domains(最省心)。

但实际操作时,又是一堆坑……


第五章:实施过程

第 1 步:注册域名

我已经有了一个域名(<your-domain>),指向 Cloudflare 的名称服务器。

这一步之前就完成了。

第 2 步:写 wrangler.toml

name = "<your-worker-name>"
compatibility_date = "<your-compatibility-date>"
main = "src/index.ts"

assets = { 
  directory = "<path-to-ui-dashboard>", 
  binding = "ASSETS", 
  html_handling = "auto-trailing-slash", 
  not_found_handling = "single-page-application" 
}

[[r2_buckets]]
binding = "<your-r2-binding-name>"
bucket_name = "<your-bucket-name>"

# ⭐ 关键配置:自定义域名
[[routes]]
pattern = "<your-domain>"
custom_domain = true
zone_name = "<your-domain>"

[observability]
enabled = true

⚠️ 上面的配置里,所有可能暴露身份的地方都用 <placeholder> 标记。请根据自己实际情况替换。

第 3 步:从正确的目录部署

这里有个隐藏的坑:

<your-project-folder>/
├── wrangler.toml      ← ❌ 这个不能用
└── template/
    └── wrangler.toml  ← ⭐ 真正的配置文件

我第一次从外层目录部署,wrangler 报错找不到 assets。

教训找到真正可部署的根目录(带 assets 配置的那个)。

第 4 步:第一次 wrangler deploy

$ cd <your-project-folder>/<deploy-root>
$ wrangler deploy

⛅️ wrangler <latest-version>
───────────────────
🌀 Building list of assets...
✨ Read N files from the assets directory
Total Upload: <X> KiB / gzip: <Y> KiB

Uploaded <your-worker-name> (7.x sec)

✘ [ERROR] Hostname '<your-domain>' already has externally managed DNS records
   (A, CNAME, etc). Delete them first or try a different hostname.
   [code: 100117]

结果

  • ✅ Worker 代码上传成功
  • ❌ Custom Domain 添加失败(域名已有 DNS 记录)

第 5 步:清理 DNS

错误说域名已有 DNS 记录。我去 Dashboard 看了看,确实有之前手动加的 AAAA 记录。

操作

  • 在 Cloudflare Dashboard → DNS → 记录
  • 删除所有现有记录(保留 NS
  • 让 wrangler 重新创建

第 6 步:第二次 wrangler deploy

$ wrangler deploy

Uploaded <your-worker-name> (7.92 sec)

这次成功了

Cloudflare 自动创建了:

  • ✅ A 记录(指向 Cloudflare 代理 IP)
  • ✅ AAAA 记录(指向 Cloudflare 代理 IP)
  • ✅ SSL 证书

第 7 步:测试访问

https://<your-domain>/

页面加载出来了!

✅ 网盘登录页
✅ Basic Auth 验证通过
✅ 文件列表显示
✅ 上传功能正常
✅ 下载功能正常

第六章:那个诡异的 CNAME 错误

期间的小插曲

在清理 DNS 的过程中,我试图手动加一条 A 记录:

类型: A
名称: @
内容: 192.0.2.1(RFC 5737 测试 IP,不会真的访问到)
代理: 已代理

报错

A CNAME record with that host already exists.

我懵了

我明明没创建 CNAME 啊?

用 dig 查了:

$ dig <your-domain> CNAME
# 什么都没有

但 Cloudflare Dashboard 说有 CNAME。

真相:CNAME Flattening

Cloudflare 有个特殊机制叫 CNAME Flattening

  • 允许在根域名(@)放 CNAME 记录
  • 在 DNS 解析时自动展开为 A 记录
  • 外部 dig 看不到(flatten 了)
  • Cloudflare 内部仍按 CNAME 追踪

这个隐藏的 CNAME 来自我之前的 wrangler deploy 失败:

  • ✅ Worker 代码上传成功
  • ⚠️ Custom Domain 部分成功(CNAME 已写入)
  • ❌ A/AAAA 记录被冲突阻止

解决方法

  1. 在 Dashboard → DNS → 记录
  2. 找到隐藏的 CNAME(即使 dig 看不到)
  3. 删除它
  4. 重新 wrangler deploy

教训

"看不到的 CNAME,不代表不存在。" "Cloudflare Dashboard 永远是最权威的 DNS 状态。"


第七章:国内访问测试

关键验证

部署成功后,最重要的一步:国内能否访问?

我用家里的宽带测试:

浏览器输入 https://<your-domain>/
加载中...
加载中...
✅ 网盘登录页!

能访问!

我又用手机 4G 测试:

✅ 能访问!

我让朋友从国内不同网络测试:

  • ✅ 电信宽带:能访问
  • ✅ 移动 4G:能访问
  • ✅ 联通宽带:能访问

全部能访问!

验证成功

对比表

| 域名类型 | 国内访问 | 原因 | |---------|---------|------| | *.workers.dev 子域 | ❌ Connection reset | GFW 精准干扰 workers.dev | | <your-domain> 自定义域名 | ✅ 正常访问 | Cloudflare 代理 IP 不在 GFW 黑名单 |

金句

"昨天死在网络上,今天活过来了。"


第八章:技术栈全景

最终的技术栈是这样的:

┌─────────────────────────────────────────────────┐
│              用户设备(任意)                      │
└──────────────────────┬──────────────────────────┘
                       │ HTTPS 请求
                       ↓
┌─────────────────────────────────────────────────┐
│           Cloudflare Edge Network                │
│                                                   │
│   ┌─────────────────────────────────────┐       │
│   │  <your-domain> 域名                 │       │
│   │  DNS: A + AAAA → Cloudflare 代理 IP │       │
│   │  (这些 IP 不在 GFW 黑名单)           │       │
│   └──────────────┬──────────────────────┘       │
│                  │                                │
│                  ↓                                │
│   ┌─────────────────────────────────────┐       │
│   │  Worker: <your-worker-name>        │       │
│   │  ├─ Basic Auth: admin/<password>    │       │
│   │  └─ R2 Binding: <your-binding>      │       │
│   └──────────────┬──────────────────────┘       │
│                  │                                │
└──────────────────┼────────────────────────────────┘
                   ↓
       ┌───────────────────────────┐
       │  Cloudflare R2 Bucket      │
       │  Name: <your-bucket>       │
       └───────────────────────────┘

关键点

  1. 域名指向 Cloudflare CDN(不是 workers.dev 子域)
  2. Cloudflare CDN 的 IP 段 在国内可以访问
  3. Worker 跑在 Cloudflare 边缘(不限速、不限额)
  4. R2 作为存储(10GB 免费,零流量费)

第九章:六个坑总结

整个过程我踩了 6 个坑:

坑 1:DNS 只有 AAAA,没有 A

问题:国内 IPv4 用户访问不到

根因:之前只配了 AAAA 记录(IPv6)

教训国内部署必须配 A 记录(IPv4)

坑 2:Custom Domain 冲突已有 DNS

问题:错误码 100117,Custom Domain 添加失败

根因:域名已有 DNS 记录,Cloudflare 要求"干净"

教训先用 wrangler 创建 Custom Domain,先不要手动配 DNS

坑 3:CNAME Flattening 隐藏陷阱

问题:添加 A 记录报 CNAME 冲突,但 dig 看不到 CNAME

根因:CNAME Flattening 机制,外部看不到,Dashboard 可见

教训Cloudflare Dashboard 是权威

坑 4:Token 权限分层

问题:DNS API 调用失败

根因:Token 只有 Account 权限,缺 Zone 权限

教训Account 级别 ≠ Zone 级别

坑 5:wrangler 部署半成品状态

问题:wrangler 报错,但 Worker 已经上传

根因:失败时部分资源已创建

教训失败重试前,先清理残留状态

坑 6:部署目录选择错误

问题:wrangler 找不到 assets

根因:从外层目录部署

教训找到真正可部署的根目录


第十章:后期维护

部署成功只是开始,维护才是长期工程。

1. 定期检查 Worker 状态

# 查看 Worker 列表
curl "https://api.cloudflare.com/client/v4/accounts/<your-account-id>/workers/scripts" \
  -H "Authorization: Bearer <your-token>"

# 实时日志(需要登录)
wrangler tail

⚠️ API 调用时不要在公开场合贴出真实的 Account ID 和 Token。本地使用即可。

2. 定期检查 DNS

dig <your-domain> A +short
dig <your-domain> AAAA +short

如果 IP 变了,可能是 Cloudflare 调整了。

3. 定期轮换密码

每 3-6 个月换一次密码:

# 修改 src/index.ts
# password: "<new-strong-password>"
wrangler deploy

4. 备份 wrangler.toml

这是部署的核心配置,必须备份到 Git 或密码管理器。

5. 监控 R2 用量

R2 免费额度 10GB。在 Dashboard → R2 → Bucket → Metrics 查看。

6. 升级网盘 UI 库

cd <your-project-folder>/<deploy-root>
npm update <your-ui-package-name>
wrangler deploy

7. 灾备方案

万一账号被封

备份清单:

  • wrangler.toml
  • src/index.ts
  • R2 Bucket 内的文件(定期下载到本地)

恢复:

  • 重新创建 Worker
  • 重新上传文件

8. 安全审计清单

每季度检查一次:

  • [ ] Worker 密码强度
  • [ ] Token 是否轮换
  • [ ] R2 Bucket 访问权限
  • [ ] DNS 记录是否被篡改
  • [ ] SSL 证书状态

第十一章:核心思路回顾

整个解决过程的核心思路

问题: 国内访问 Cloudflare Worker
  ↓
诊断: GFW 屏蔽的是什么?
  ↓
发现: workers.dev 子域被墙,Cloudflare CDN 本身没事
  ↓
思路: 用自己的域名代替 workers.dev
  ↓
方案: Cloudflare Custom Domains 自动管理 DNS
  ↓
实施: wrangler.toml + wrangler deploy
  ↓
验证: 国内访问测试
  ↓
结论: ✅ 国内可访问!

关键洞察

GFW 屏蔽的是"路径",不是"平台"。

  • ❌ 屏蔽 *.workers.dev
  • ✅ 但 Cloudflare CDN 的 IP 段可以用
  • ✅ 只要入口域名不在黑名单,就能绕过

给同样困境的人

如果你也有 Cloudflare Worker 国内访问不了的问题:

  1. 先诊断:GFW 屏蔽的是域名还是 IP?
  2. 找思路:能不能用别的入口?
  3. 用 Cloudflare Custom Domains:最省心
  4. 验证:多网络、多设备测试

第十二章:金句合集

整个过程我写了一些自认为有道理的话:

"问题不在物理层,而在配置层。"

"GFW 屏蔽的是路径,不是平台。"

"昨天死在网络上,今天活过来了。"

"Routes 不创建 DNS,Custom Domains 才创建 DNS。"

"看不到的 CNAME,不代表不存在。"

"wrangler 失败时,会有半成品状态残留。"

"国内部署:A 记录不是可选,是必须。"

"写文章时记得模糊化地址信息,避免被 GFW 溯源。"


第十三章:下一步

部署成功后,我计划做这些事:

  • [ ] 把密码从源码移到 wrangler secret(避免 git 泄露)
  • [ ] 启用 Cloudflare Access 做第二层保护
  • [ ] 升级到更高级的网盘 UI(具体名字就不说了)
  • [ ] 写一篇关于密码管理的博客
  • [ ] 监控 Worker 流量和性能

结语

写完这篇文章,我有一种奇怪的感觉:

问题解开了,反而没什么戏剧性了

没有那种"灵光一现"的顿悟时刻, 也没有那种"终于找到答案"的狂喜。

只是:

  • 一步一步诊断
  • 一个一个坑踩
  • 一条一条命令敲
  • 一遍一遍测试

然后,问题就解决了

也许这就是技术的本来面目:

不是灵光一现,是日拱一卒。 不是顿悟,是积累到一定程度后的水到渠成。

如果你也在类似的问题里挣扎,希望这篇文章能给你一些思路。

GFW 不是不可逾越的墙,它是规则规则可以被理解,理解后就可以被绕过

这大概就是技术人面对限制时,最朴素的信念。

⚠️ 最后提醒:本文所有地址、域名、Worker 名、目录名都用占位符代替。这不是偷懒,是保护自己

想想看:如果你写一篇文章,把自己的 Cloudflare 自定义域名公布出来,GFW 维护者看到后,只要在 workers.dev 黑名单旁边再加一个域名,你的服务就挂了。

沉默是金。分享思路,保留细节。


写于 2026 年 6 月 22 日深夜 从"死在了网络上"到"活过来了" 🚀 保持沉默,安全第一 🔒


📎 附录:完整配置文件(已脱敏)

wrangler.toml

name = "<your-worker-name>"
compatibility_date = "<your-compatibility-date>"
main = "src/index.ts"

assets = { 
  directory = "<path-to-ui-dashboard>", 
  binding = "ASSETS", 
  html_handling = "auto-trailing-slash", 
  not_found_handling = "single-page-application" 
}

[[r2_buckets]]
binding = "<your-r2-binding-name>"
bucket_name = "<your-bucket-name>"

[[routes]]
pattern = "<your-domain>"
custom_domain = true
zone_name = "<your-domain>"

[observability]
enabled = true

src/index.ts

import { <your-ui-library> } from "<your-ui-package-name>";

export default <your-ui-library>({
  readonly: false,
  basicAuth: {
    username: "<your-username>",
    password: "<your-strong-password>",
  },
});

部署命令

cd <your-project-folder>/template
export PATH=/Users/zhangfengrun/nodejs/bin:$PATH
export CLOUDFLARE_API_TOKEN="<your-token>"
export CLOUDFLARE_ACCOUNT_ID="<your-account-id>"
wrangler deploy

占位符对照表

| 占位符 | 替换为 | |--------|--------| | <your-domain> | 你的自定义域名 | | <your-worker-name> | 你的 Worker 名称 | | <your-account> | 你的 Cloudflare 账户子域 | | <your-account-id> | 你的 Cloudflare Account ID | | <your-token> | 你的 Cloudflare API Token | | <your-bucket-name> | 你的 R2 Bucket 名称 | | <your-r2-binding-name> | wrangler.toml 里的 R2 binding 名 | | <your-project-folder> | 你的部署项目文件夹 | | <deploy-root> | 你的部署根目录(包含 wrangler.toml 的目录)| | <path-to-ui-dashboard> | 你的网盘 UI 库的 dashboard 路径 | | <your-ui-library> | 你使用的网盘 UI 库的主函数 | | <your-ui-package-name> | 你使用的网盘 UI 库的 npm 包名 | | <your-username> | 你的访问用户名(不一定非是 admin)| | <your-strong-password> | 你的 Worker 访问密码(建议 11+ 字符,含特殊字符)| | <your-compatibility-date> | 你的 wrangler 兼容性日期 | | <latest-version> | wrangler 最新版本号 |

#Cloudflare#Workers#GFW#国内访问#自定义域名#个人云盘

分享这篇文章