Cloudflare Telegram Web Proxy 部署教程:Pages 主程序 + Worker 转发 + 自动优选 DNS 全流程

前言

cftgwebproxy 是一个开源的 Telegram Web Proxy 部署方案:用 Cloudflare 的免费资源把 TG 的 MTProto 代理跑起来,客户端填一个自己的域名就能连。

它和我之前写过的单文件 Worker 代理方案不太一样,最大的特点是拆成三层:

组件 跑在哪 职责
telegram_webproxy.js Cloudflare Pages(须改名 _worker.js) 真正的代理主程序,负责 MTProto 帧协议与连接 Telegram 各 DC
webproxy-relay.js Cloudflare Workers 纯转发层,让你的优选域名能接到 Pages 主程序上
domain-dns.js Cloudflare Workers + Cron 定时把优选 IP 同步到域名的 A 记录

为什么要这么拆?项目 README 给了两个理由,都挺实在:

  1. 避开 1101 / 522:Worker 直接长时间挂着 WebSocket 代理,容易触发 Worker 异常(1101)和源站超时(522),把主程序放 Pages 能显著降低这类报错;
  2. 额度分开算:按作者的说法,Pages 和 Worker 的请求额度是分开计算的,拆开后总容量更大。

最终链路是这样:

1
2
3
4
5
Telegram 客户端
→ 你的优选域名(A 记录由 Cron 自动更新)
→ Worker 转发层(webproxy-relay.js)
→ Pages 主程序(telegram_webproxy.js)
→ Telegram 服务器(KWS / DC)

本文依据仓库 README 与三个脚本的源码整理,环境变量规则、接口路径、校验逻辑均以源码为准。部署时请以仓库最新提交为准。


准备工作

开干之前,把这几样东西备齐:

项目 说明
Cloudflare 账号 建议准备 2 个:一个放 Pages 主程序,另一个放 Worker 转发层(README 的建议做法,能进一步分散风控与额度)
一个托管在 Cloudflare 的域名 用来做「优选域名」。关键限制:这个域名所在的 Zone 必须和 Worker 在同一个 Cloudflare 账号下,否则加不了路由
优选 IP 源 一个返回 IP:Port 文本列表的接口(每行一条,可带 # 注释)。用于第三层的自动 DNS
本地环境 只用面板操作的话什么都不用装;想用命令生成密钥,需要 openssl

先生成两个密钥,后面两个组件都要用:

1
2
3
4
5
# BOOT:建议 ≥16 位随机字符串
openssl rand -hex 24

# SECRET:32 位小写十六进制(openssl rand -hex 16 刚好 32 个字符)
openssl rand -hex 16

环境变量的两条硬规则(源码里写死的校验,填错直接 500):

  • SECRET:必须是 32 位小写十六进制(0-9a-f),或者 dd + 32 位十六进制(共 34 位)
  • BOOT:任意非空随机字符串,建议 16 位以上

关于 dd 前缀:它是 MTProxy 的可选模式,作用是启用随机填充(Random Padding),把包长随机化。填的时候必须带上 dd 两个字面字符,共 34 位;不带前缀就是标准的 32 位 secret。客户端里要填和这里完全一致的字符串。


第一步:部署 Pages 主程序

这一层是整个方案的核心。

1.1 新建 Pages 项目

Cloudflare 控制台 → Workers 和 Pages → 创建 → Pages → 上传资产(直接上传,最省事)。

1.2 文件必须改名成 _worker.js

这是最容易踩的坑:把 telegram_webproxy.js 重命名为 _worker.js,放在上传目录的根目录。

原因:Pages 的 Functions 高级模式只认根目录下这个固定文件名,名字不对就完全不会执行你的代码,页面只会返回静态 404 或空白页。

1
2
上传目录/
└── _worker.js ← telegram_webproxy.js 改名而来,必须在根目录

1.3 配置两个加密变量

项目 → 设置 → 变量和机密(Environment variables),两个都建议用**加密(Secret)**方式添加:

变量名 类型 值
BOOT 机密 上面 openssl rand -hex 24 的输出
SECRET 机密 上面 openssl rand -hex 16 的输出(可加 dd 前缀)

保存后重新部署一次,让变量生效。

1.4 验证主程序

部署完成后访问:

1
https://<项目名>.pages.dev/health

返回下面这行就说明主程序活了:

1
{"ok":true}

如果这里就是 500,别急着往下做——先回去检查 BOOT / SECRET 的格式(最常见就是 SECRET 长度不对或包含大写字母)。

1.5 主程序到底做了什么(看懂再调)

顺手把源码里的关键设计说一下,排障时很有用:

  • 对外接口只有四个:/(桥接页)、/health、/api/v1/session、/api/v1/ws,其他路径一律 404
  • /api/v1/session:用 BOOT 派生的 Bearer 令牌做鉴权,POST 一段二进制握手数据,成功后通过响应头 X-Session-Token 返回会话令牌
  • /api/v1/ws:真正的数据通道,要求 Upgrade: websocket,且子协议必须形如 tproxy-lane-v1.<令牌>.<通道号>;Origin 若不等于自身 Origin 会被拒绝
  • 帧协议:8 字节帧头(类型 + 会话 ID + 长度),单帧上限 1 MB,单条消息最多 4096 帧;每个令牌最多 128 条并发通道、累计 4096 个通道号
  • 安全头:返回的桥接页带 sandbox 的 CSP,且 frame-ancestors 只允许 http://127.0.0.1:*——这是给 Telegram Desktop 的本地页面做嵌入用的

所以:直接用浏览器打开 https://你的域名/ 看到一个几乎空白的 “Connection” 页是正常的。 那个页面的完整版只有在带上正确签名的 ?bridge= 参数时才会渲染,而签名由转发层按 SECRET 计算,客户端会通过本地页自动完成——不需要你手动拼。


第二步:部署 Worker 转发层

这一层的唯一任务是:把你的域名请求原样转给 Pages 主程序,并把返回内容里的地址改写回你的域名。

2.1 新建 Worker

切换到第二个 Cloudflare 账号(如果你按建议分了两个),Workers 和 Pages → 创建 Worker,把 webproxy-relay.js 的内容整体粘贴进去,部署。

2.2 配置变量

变量名 类型 值
HOST 文本(明文) Pages 主程序的默认域名,形如 xxxx.pages.dev,不要带 https://、不要带路径、不要填自定义域名
SECRET 机密 必须和 Pages 主程序的 SECRET 完全一致

HOST 填错的典型症状是:域名访问全 404 或 502。代码里会自动剥掉协议头和端口,但填自定义域名仍然会错——它要求的是 *.pages.dev 那个原始域名。

2.3 绑定路由

Worker → 设置 → 域和路由 → 添加路由,把你的优选域名挂上去:

1
2
你的域名/*          ← 对应根域名 @
*.你的域名/* ← 对应任意子域名

两条都加,覆盖面最全。注意:该域名的 Zone 必须和这个 Worker 在同一账号,否则界面根本不会让你选这个 Zone,这是 Cloudflare 的限制,不是项目的问题。

2.4 验证转发

1
2
curl https://你的域名/health
# 期望:{"ok":true}

能通说明「域名 → Worker → Pages」这条链已经打通了。

2.5 转发层做的三件细活

源码里有几个处理值得知道,出问题时能对上号:

  1. 重签桥接令牌:客户端带的 ?bridge= 是按「当前域名」用 HMAC-SHA256 算的,转发层会校验并在转发时改写成按 pages.dev 域名计算的版本,这样桥接页在自定义域名下也能正常渲染;
  2. 改写回跳地址:遇到 3xx 重定向,把 Location 里的 pages.dev 换回你的域名;
  3. 改写 HTML:对 text/html 响应做字符串替换,把 https://页域名 与 wss://页域名 全部换回你的域名,同时清掉 Content-Length、Content-Encoding、ETag 等会因为改写而失效的头。

它还严格校验 WebSocket 的 Origin:只有等于 https://你的域名 才放行,否则直接 404。所以用其他域名或裸 IP 去连是连不上的,这是设计如此。


第三步:让优选 IP 自动更新(可选但强烈推荐)

Cloudflare 的 Anycast 会让你落到就近节点,但不同 IP 段在你的网络环境下质量差很多。这一层用 Cron 定时把「测得快的 IP」写进 A 记录,避免手动维护。

3.1 建一个有 DNS 编辑权限的 Token

Cloudflare 控制台 → 我的个人资料 → API 令牌 → 创建令牌,模板选「编辑区域 DNS」:

  • 权限:Zone → DNS → Edit
  • 区域资源:只选你的优选域名这一个 Zone(最小权限)
  • 创建后把令牌复制保存

顺手记下这个 Zone 的 Zone ID(域名概览页右下角)。

3.2 新建 Worker 并改三个常量

再建一个 Worker,粘贴 domain-dns.js,改开头这几个常量:

1
2
3
4
5
6
7
8
9
10
11
// 优选 IP 源:返回文本,每行 IP:Port,可带 # 注释
const URL = 'https://你的IP源地址';

// 你的优选域名
const DOMAIN = 'example.com';

// 会分别同步 @ 根域名 和 * 通配子域名
const RECORD_NAMES = [
{ label: '@', fqdn: DOMAIN },
{ label: '*', fqdn: `*.${DOMAIN}` }
];

3.3 配置变量与 Cron

变量名 类型 值
ZONE_ID 文本 你的优选域名 Zone ID
API_TOKEN 机密 上一步创建的 DNS 编辑令牌

然后在 Worker → 触发器 → Cron 触发器 里加一条,例如每 30 分钟:

1
*/30 * * * *

免费计划同样支持 Cron 触发器,但每次触发都会计入请求次数,别设得过于频繁(比如每分钟一次完全没必要,优选 IP 的变动频率远低于此)。

3.4 这层脚本的逻辑(便于自查)

  • 拉取 IP 源文本,只保留端口为 443 的条目(其他端口直接忽略)
  • 对 @ 和 * 两条记录:分页读出当前所有 A 记录 → 删掉不在目标列表里的 → 补上缺的
  • 新记录 TTL 设为 60 秒,改动后一分钟内全网生效
  • 会容忍 Cloudflare 的「记录已存在 / 内容重复」类错误码,不会因为并发重复创建而整体失败

3.5 验证

  1. Worker 的 日志 里能看到每次 Cron 的执行结果(同步了哪些 IP、有没有报错)
  2. 本地查一下记录:
1
nslookup 你的域名 1.1.1.1

返回的 IP 应该和你的优选 IP 源里 443 那批对得上。


第四步:客户端配置

到这一步,服务端已经就绪。客户端只需要三个字段:

字段 填什么
服务器 / Server 你的优选域名(例如 example.com)
端口 / Port 443
密钥 / Secret 和 Pages 主程序一致的 SECRET;如果用了 dd 前缀,必须连 dd 一起填满 34 位

Telegram Desktop(Windows / macOS / Linux)

设置 → 高级 → 连接类型 → 代理 → 添加代理 → 类型选 MTProto → 填入上面三项 → 保存 → 选中该代理。

Telegram Android / iOS

设置 → 数据和存储 → 代理(部分版本在「聊天设置 → 代理」)→ 添加代理 → MTProto → 填入三项 → 启用。

判断是否真的连上了

  • 客户端代理列表里该条目显示已连接 / Connected
  • 打开任意频道能正常收发消息
  • 服务端侧可以访问 https://你的域名/health 确认主程序存活

如果客户端一直转圈:先确认 https://你的域名/health 返回 {"ok":true},再检查客户端里的 secret 有没有少一位或多一位(尤其 dd 前缀很容易漏)。


额度与性能

项目 说明
Workers 免费额度 每天 10 万次请求,日常个人使用远远够
Pages 免费额度 同样有每日请求上限;作者说明与 Worker 分开计算
Cron 每次触发算一次请求,频率别拉满
WebSocket 长连接本身就是低请求数、高流量模型,比短轮询省额度

几个实测向的优化建议:

  • 优选 IP 源的质量决定了 90% 的体验。换几个源对比延迟再定,别用来源不明的列表
  • Cron 频率 30–60 分钟足够,IP 失效通常不会在几分钟内发生
  • 如果你只是自己用,只加 @ 根域名那条记录也行,少一条记录少一次 API 调用
  • 出现 1101 / 522 时,优先检查是不是有人在猛拉流量,或 Workers 侧并发太高;主程序留在 Pages 是这套方案的核心思路,不要改动

常见故障排查

/health 返回 500

BOOT 或 SECRET 缺失、格式不对。重点查 SECRET:必须 32 位小写十六进制,或 dd + 32 位;有大写字母、长度 31/33 都会失败。

访问域名全部 404

三种可能,按顺序排查:

  1. Worker 路由没加(或缺 * 通配那条)
  2. 域名 Zone 与 Worker 不在同一 Cloudflare 账号
  3. HOST 填错了(必须是最原始的 *.pages.dev,不是自定义域名)

页面能开,但 Telegram 一直连不上

  • Worker 里 SECRET 与 Pages 里不一致 —— 这是最常见原因
  • 客户端 secret 填错(dd 前缀漏填或重复)
  • WebSocket 被中间层拦了:转发层要求 Origin 严格等于 https://你的域名

从浏览器打开 https://你的域名/ 是一片空白

正常现象。桥接页需要合法签名的 ?bridge= 参数才会完整渲染,客户端会自动处理,不需要手动拼。

DNS 不自动更新

  • API_TOKEN 权限不足(必须含该 Zone 的 DNS Edit)
  • ZONE_ID 填错
  • Cron 没启用,或最近一次执行报错(看 Worker 日志)
  • IP 源返回的条目里没有 :443,脚本会全部过滤掉

偶尔 1101 / 522

1101 是 Worker 运行时抛异常,522 是源站超时。两个都属于「Worker 直接扛代理」的固有问题,这也是本方案把主程序放 Pages 的原因。降低并发、别把域名公开给大量陌生人使用,能明显缓解。

想换 SECRET 怎么办

必须同时改两处(Pages 主程序 + Worker 转发层),改完重新部署,然后通知所有客户端更新 secret。只改一边会导致全部连不上。


安全与注意事项

  • SECRET 就是这台上网的钥匙:拿到「域名 + secret」的任何人都在白嫖你的代理,不要发到公开群组或仓库里
  • API_TOKEN 只给最小权限:只勾选该 Zone 的 DNS 编辑,别用全局 API Key
  • 三个组件建议分账号部署,一处泄露不至于全线崩塌
  • 密钥用「机密(Secret)」类型存,不要写死在代码里再上传
  • 本项目用于合法的通信与学习用途,请自行遵守所在地法律法规以及相关平台的服务条款

小结

把整套流程压缩成一张清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
① Pages:telegram_webproxy.js → 改名 _worker.js → 传根目录
变量:BOOT(随机串)、SECRET(32 位 hex 或 dd+32 位)
验证:/health → {"ok":true}

② Worker:webproxy-relay.js
变量:HOST = xxx.pages.dev、SECRET = 同上
路由:域名/*、*.域名/*
验证:curl https://域名/health

③ Worker + Cron:domain-dns.js
改:URL、DOMAIN
变量:ZONE_ID、API_TOKEN(DNS Edit)
Cron:*/30 * * * *(按需)
验证:nslookup 看 A 记录

④ 客户端:MTProto · 域名 · 443 · SECRET

这套方案最值得学的地方,其实是它「用 Pages 扛长连接、用 Worker 只做转发」的工程取舍——1101/522 这类边缘平台的老问题,靠架构拆分绕过去,比在单文件里加一堆重试优雅得多。想自己搭一套 TG 代理的话,按上面四步走,半小时能跑通。


参考资料

推荐链接