利用 Cloudflare Tunnel 与 WebSocket 入站实现内网服务的零公网 IP 暴露

举报
yd_242888859 发表于 2026/10/08 15:16:13 2026/10/08
【摘要】 前言在日常开发和自托管场景中,我们经常遇到这样的问题:手头有一台没有公网 IP 的机器(家宽 NAS、校园网服务器、云上容器等),想把它上面的服务安全地暴露出去,又不想在路由器上开端口转发。本文分享我实践过的一套方案:Cloudflare Tunnel(Argo)+ WebSocket 类入站代理,实现零公网 IP、零端口开放的服务暴露,并记录了排查“隧道通但业务不通”问题的完整思路。一、整...

前言

在日常开发和自托管场景中,我们经常遇到这样的问题:手头有一台没有公网 IP 的机器(家宽 NAS、校园网服务器、云上容器等),想把它上面的服务安全地暴露出去,又不想在路由器上开端口转发。本文分享我实践过的一套方案:Cloudflare Tunnel(Argo)+ WebSocket 类入站代理,实现零公网 IP、零端口开放的服务暴露,并记录了排查“隧道通但业务不通”问题的完整思路。

一、整体架构

用户客户端
   │  TLS
   ▼
Cloudflare Edge(域名解析,代理开启)
   │  WSS 加密隧道(cloudflared 主动外连,无需入站端口)
   ▼
本机 cloudflared 进程
   │  HTTP 回源 localhost:PORT
   ▼
服务端应用(支持 WebSocket 路由分发)

核心思想:让云端主动“出站”连接 Cloudflare。cloudflared 与边缘节点建立长连接后,所有访问指定域名的流量都会通过这条隧道回源到本机,防火墙只需要允许出站 7844 端口即可。

二、实施步骤

1. 创建隧道

在 Cloudflare Zero Trust 控制台创建 Tunnel,得到 token(或 TunnelSecret JSON),在服务器上运行:

cloudflared tunnel --no-autoupdate --protocol http2 run --token <TOKEN>

然后把域名(如 app.example.com)绑定到该隧道,回源地址填 http://localhost:8001。

2. 服务端多协议复用一个端口

为了在一台机器、一个隧道端口上承载多种业务路由,我用了一个带 fallback 的入站设计:

  • 主入站监听 8001,按路径分发:

    • /vless-argo → 内部 3002(WebSocket)

    • /vmess-argo → 内部 3003(WebSocket)

    • /trojan-argo → 内部 3004(WebSocket)

    • 其他流量 → 直接回落 3001

这样 Cloudflare 回源只打一个端口,内部路由互不干扰,运维上非常清爽。

3. 客户端配置要点

以 vmess + ws + tls 为例,客户端关键参数:

参数 值
地址 优选域名/IP(如 saas 类优选域名)
端口 443
传输 WebSocket
path /vmess-argo?ed=2560(ed 参数启用 0-RTT)
Host/SNI 隧道域名

注意:优选域名解析到的是 Cloudflare 边缘节点,不是你的服务器;真正的回源靠 SNI + Host 匹配到隧道。

三、踩坑记录:隧道通、节点不通

这是最有价值的一部分。我遇到过控制台显示连接器在线、curl https://隧道域名/ 也有响应,但实际业务握手失败的情况。排查思路如下:

1. 先分层定位

# ① 隧道层:本地回源是否正常
curl -i -H "Upgrade: websocket" -H "Connection: Upgrade" \
     -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
     -H "Sec-WebSocket-Version: 13" http://127.0.0.1:8001/vmess-argo
# 期望 101

# ② 公网层:走域名测试
curl -i --http1.1 -H "Upgrade: websocket" ... https://app.example.com/vmess-argo
  • ① 通、②不通 → 问题在 Cloudflare 回源配置(回源端口、ingress 规则)

  • ① 就不通 → 问题在本机服务(大概率是入站没起来)

2. 本机入站“静默失败”的经典陷阱

服务端二进制用 nohup app -c config.json >/dev/null 2>&1 & 启动,如果 8001 被其他进程占用,核心会启动失败,但日志被吞掉了,主进程还活着,看起来一切正常。

解决:启动后必须验证端口真的在监听:

ss -tlnp | grep -E ':(8001|3001|3002|3003|3004)'

我在脚本里加了预检逻辑:启动前逐端口尝试绑定,被占用直接报错提示换端口;启动后轮询探测端口进入 LISTEN 状态才继续。自此再没被这个问题坑过。

3. 530 vs 502 的含义

  • 530:隧道本身没连上(cloudflared 进程死了或 token 失效)→ 查 cloudflared

  • 502:隧道通,但回源失败(服务没监听 / 端口不对)→ 查本机服务和回源配置

记住这两个码,排查速度能快一倍。

四、稳定性建议

  1. 固定隧道 + 固定回源端口:临时隧道(trycloudflare.com)域名每次重启都变,订阅类场景务必用固定隧道。

  2. 子进程看护:cloudflared 和业务核心用 systemd 或 pm2 托管,并配置崩溃自动重启。

  3. 高位端口防冲突:回源端口选 50000+ 的高位端口,避开容器环境常见的 8000/8080。

  4. WebSocket 路径加随机化:路径里带一段随机串,减少被扫描器探测的概率。

结语

整套方案零公网 IP、零端口暴露,所有流量都走 TLS 加密的 Cloudflare 网络,适合自托管面板、家庭 NAS 服务等场景。最大的心得是:“进程在跑”不等于“服务可用”,端口监听验证和分层 curl 探测是排障的两个基本功。希望这篇分享能帮你少踩几个坑。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。