\---  
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 代理，改直连。

\---  
自我介绍：理科出身的程序猿，前交易平台工程师，爱看脱口秀。最骄傲的事：4000 元买入 8 个 BTC。
推特：https://x.com/yedabinbinah  请收藏，该提示词会不断更新
\---