\---  
name: xray-cf-tunnel-node  
description: 在无公网IP的机器上用 Cloudflare 隧道自建 Xray 节点。  
version: 1.0.0  
author: Hermes Agent  
license: MIT  
platforms: [linux]  
metadata:  
  hermes:  
    tags: [proxy, xray, cloudflare, tunnel, vless]  
\---

# Xray + Cloudflare Tunnel 节点自建

在一台**只能出站、没有公网入站**的机器(云沙箱 VM、容器、NAT 后的家用机)上,用 Cloudflare 命名隧道把一个 VLESS+WS+TLS 节点开到公网。不需要买 VPS、不需要端口映射、不需要任何第三方中转服务器。整条链只依赖:这台机器、一个 Cloudflare 账号、一个入口域名(或不要域名的快速隧道)。

## When to Use

- 机器能正常出站上网,但没有公网入站端口。
- 想要一个完全归自己、不经第三方服务器的节点。
- **不要用于**:机器连 443 都出不去的环境;需要大流量/极低延迟的场景(隧道多一跳 CF)。
- **不要用 Railway 等 PaaS** 部署代理——官方 AUP 明文禁止,封号白忙。
- **不要借别人的服务器中转**,除非对方明确同意。

## 前置条件

- Linux x86_64/arm64,能访问 GitHub releases。
- Cloudflare 账号(免费计划)+ 一个托管在 CF 的域名(方案 A);方案 B 不需要。
- `uuidgen`、`curl`、`unzip`(一般预装)。
- 一个支持 VLESS+WS+TLS 的客户端(Shadowrocket、v2rayN、Nekoray 等)。

## 入口方案选择(先问用户)

开始部署前先让用户二选一:

|       | 方案 A:命名隧道 + 免费域名                                   | 方案 B:快速隧道       |
| ----- | -------------------------------------------------- | --------------- |
| 域名    | 需要(DigitalPlat 免费域如 dpdns.org / us.kg,或自有域名托管到 CF) | 不需要             |
| CF 账号 | 需要                                                 | 不需要             |
| 固定域名  | ✅ 永久不变                                             | ❌ 随机四词,进程重启就变   |
| 自动保活  | ✅ 可全自动                                             | ❌ 域名不可预测,无法自动恢复 |
| 适合    | 长期日常使用                                             | 临时/测试/一次性       |

选择依据:用户要长期用 → 方案 A;只想快速验证或临时用几小时 → 方案 B。两方案可以同时跑,互不冲突。

## Procedure

### 第 0 步:安装二进制

1. 建 kit 目录,下载对应架构的 [Xray-core](https://github.com/XTLS/Xray-core/releases)(`Xray-linux-64.zip`)解出 `xray` 到 `~/xray-kit/bin/xray`,下载 [cloudflared](https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb) 备份到家目录(重置后可自动重装)。
   - 完成标准:`~/xray-kit/bin/xray version` 和 `cloudflared --version` 都有输出。
2. 用系统包安装 cloudflared:`sudo dpkg -i ~/xray-kit/cloudflared.deb`。

### 第 1 步:Xray 服务端

1. 生成凭证并写 `~/xray-kit/state.json`(权限 600):UUID 用 `uuidgen`,WS 路径用 `openssl rand -hex 4`(8 位随机十六进制,不要用 `/ray` 这种显眼路径)。
2. 从 state.json 生成 `~/xray-kit/run/config.json`:inbound 监听 `127.0.0.1:10000`(只听本地!公网入口全交给隧道),协议 vless,传输 ws,outbound 为 freedom。
3. 启动并验活(后台方式跑,注意不要用 nohup 包装):
   ```
   curl -s -o /dev/null -w "%{http_code}" -H "Connection: Upgrade" -H "Upgrade: websocket" \
     -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
     "http://127.0.0.1:10000/<路径>"
   ```
   - 完成标准:**101**。不是 101 先修 Xray,不要往下走。

### 第 2 步(方案 A):建命名隧道

1. **拿域名**:用户没有域名时,指导其在 [dash.domain.digitalplat.org](https://dash.domain.digitalplat.org/) 注册免费域(dpdns.org / us.kg / qzz.io / xx.kg / qd.je 后缀,GitHub 登录加速人工审核),再把 NS 改到 Cloudflare;或用自有域名添加到 CF。
2. `cloudflared tunnel login`(后台跑,stdout 重定向到文件)→ 从日志抓 `https://dash.cloudflare.com/argotunnel?...` 链接发给用户 → 用户在浏览器选择域名点 Authorize → 轮询 `~/.cloudflared/cert.pem` 出现即成功。
3. `cloudflared tunnel create xray-node` → 记下隧道 ID。
4. `cloudflared tunnel route dns -f xray-node vm.<域名>`(如 `vm.dabinda.dpdns.org`)。

### 第 3 步(方案 A,关键坑):推送 ingress 规则

**CLI 创建的隧道默认 ingress 是 catch-all 503,公网会一直 503!** 必须用 API 推路由:

1. 从 `cert.pem` 的 `ARGO TUNNEL TOKEN` 段 base64 解出 JSON,取 `accountID` 和 `apiToken`。
2. `PUT /accounts/<accountID>/cfd_tunnel/<隧道ID>/configurations`,body:
   ```json
   {"config":{"ingress":[
     {"hostname":"vm.<域名>","service":"http://localhost:10000"},
     {"service":"http_status:404"}
   ]}}
   ```
3. 完成标准:响应 `success: true` 且 ingress 数组正确。

### 第 4 步(方案 A):取连接器令牌并启动

1. `GET /accounts/<accountID>/cfd_tunnel/<隧道ID>/token`(同一 apiToken),`result` 就是连接器令牌,写入 `~/xray-kit/run/cf_token.txt` 并 chmod 600。**令牌不经过聊天工具。**
2. 启动连接器(后台方式):
   ```
   unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy ALL_PROXY all_proxy
   cloudflared tunnel --no-autoupdate --protocol http2 run --token "$(cat ~/xray-kit/run/cf_token.txt)"
   ```
   - 完成标准:日志出现 4 条 `Registered tunnel connection`。
3. 排查四坑(按序):
   - **QUIC 被封**(`operation not permitted`)→ 强制 `--protocol http2`。
   - **DNS 假 IP**(198.18.x.x)→ DoH 查 `region1/region2.v2.argotunnel.com` 真实 IP(198.41.192.0/24、198.41.200.0/24),写 /etc/hosts;只读时用 `unshare -m` 挂载覆盖。
   - **平台 MITM 代理** → 启动前 unset 所有代理变量,隧道和 Xray 出站都走直连。
   - **TLS 握手反复超时** → 重启连接器换出口路径,不要死等退避。
4. **改过 ingress 后必须重启连接器**,否则它继续用旧的本地空配置,公网依然 503。

### 第 5 步(方案 B):快速隧道

1. 直接后台启动:`cloudflared tunnel --url http://localhost:10000 --protocol http2`。
2. 从日志抓 `https://xxxx.trycloudflare.com` 随机域名。
3. 完成标准:`curl -o /dev/null -w "%{http_code}" https://<随机域名>/` 返回 **404**(穿过隧道到达 Xray 的正常响应)。530 = 没注册上,000 = 网络不通。
4. 提醒用户:此域名重启即失效,客户端需重新配置。

### 第 6 步:客户端全链路拨测(别省)

1. 生成临时客户端配置:address 填入口域名,port 443,TLS + WS(Host/SNI = 入口域名,path 与服务端一致),本地 SOCKS 端口选一个空闲的(如 10808)。
2. 拨测:
   ```
   curl --socks5-hostname 127.0.0.1:<port> https://api.ipify.org        # 出口 IP 应为本机
   curl --socks5-hostname 127.0.0.1:<port> https://www.google.com/generate_204  # 204
   curl --socks5-hostname 127.0.0.1:<port> https://x.com                # 200
   ```
   - 完成标准:三项全过且出口 IP 是本机出口(不是 CF 的、不是中转的)。

### 第 7 步:交付物

1. 节点链接(path 要 URL 编码 `/`→`%2F`):  
   `vless://<UUID>@<域名>:443?encryption=none&security=tls&sni=<域名>&type=ws&host=<域名>&path=%2F<路径>&fp=chrome#节点名`
2. 订阅 = 链接 base64。二维码:`pip install "qrcode[pil]"` 后 `qrcode.make(...).save('node_qr.png')`。
3. **长链接用纯文本发,不放代码块**(手机上代码块要左右滑动、极难复制),另附 .txt 文件方便全选。

### 第 8 步(方案 A):保活

1. 写幂等 `~/xray-kit/recover.sh`,四件事:重建配置(UUID/路径从 state.json 读,重置后参数不变);WS 非 101 → 重启 Xray;连接器不在 → 拉起;连接器活着但公网连续多次非 404 → 重启连接器换出口路径。
2. 二进制、.deb、令牌、state.json 全放家目录——平台重置只保家目录。
3. cron 每 15 分钟跑一次:`(crontab -l; echo "*/15 * * * * $HOME/xray-kit/recover.sh >/dev/null 2>&1") | crontab -`。
4. 重启台账:recover 时对比 `/proc/sys/kernel/random/boot_id`,变了就记 `当前时间 - /proc/uptime` 反推的开机时刻到 `resets.log`,攒出平台重启规律。
5. **已知短板**:整机重置后 crontab 可能被清,第一次拉起可能需要手动跑一次 recover.sh。

## 安全纪律

- UUID、WS 路径、隧道令牌 = 节点的全部钥匙:只存 600 权限文件,不进聊天、不进截图、不进公开文档。
- 分享教程一律用占位符,**包括自己的真实域名**——子域名公开等于送入口地址。
- 怀疑令牌泄露:CF 仪表盘轮换隧道令牌,更新 cf_token.txt 重启连接器;链接和二维码只含 UUID,不受影响。

## Verification

- [ ] 本机 WS 握手 101
- [ ] 公网入口(域名或 trycloudflare 地址)返回 404
- [ ] 客户端全链路:出口 IP = 本机、Google 204、x.com 200
- [ ] recover.sh 手动跑一遍无报错,crontab -l 可见条目
- [ ] 二维码用手机客户端扫入可连通

## Pitfalls

- 503 ≠ 隧道没通:先查 ingress 是否推过、连接器是否重启过(见第 3/4 步),这是 CLI 建隧道最常见的坑。
- 404 是正常值(Xray 对根路径的响应),不是错误。
- 出口 IP 查出来有两个不同地址(如 119.28.71.87 / 119.28.64.191)通常是同 ASN 多出口线路,正常。
- dpkg 报锁:`fuser /var/lib/dpkg/lock-frontend` 确认无进程后直接重跑 `dpkg -i`。
- 客户端报证书错误:十有八九流量走了平台 MITM 代理,改直连。

\---