没有公网 IP,也能把家里的服务发到网上:Cloudflare Tunnel 实操

上一篇讲把域名接入 Cloudflare 时,我留了个尾巴:SSH 这类服务不想暴露源站 IP,「以后用 Tunnel 来解决」。今天就把这个坑填上。
先说结论:Cloudflare Tunnel 让你的机器主动连出去,而不是等别人连进来。所以你不需要公网 IP,不需要在路由器上开端口,防火墙甚至可以把入站全部关掉。家里的 NAS、实验室的小主机、只有内网地址的虚拟机,都能用自己的域名访问。
下面示例统一用 example.com,局域网机器用文档专用 IP 192.0.2.10,换成你自己的就行。
1. 原理:门是从里面打开的
传统做法是在路由器上做端口映射,把 80、443 开出去,等于在墙上凿个洞。Tunnel 反过来:你在机器上跑一个叫 cloudflared 的小程序,它主动向 Cloudflare 发起只出不进的连接。连接建立后,流量在这条隧道里双向流动。

访客的路径是这样的:浏览器请求 nas.example.com,DNS 里这个名字是一条 CNAME,指向 <隧道UUID>.cfargotunnel.com;请求先到 Cloudflare 边缘,再顺着隧道送到你的机器,由 cloudflared 转给本地的 http://localhost:5000。
有两点值得记住:
- 隧道是一个用 UUID 标识的持久对象,可以同时跑多个
cloudflared进程连到同一条隧道,官方叫「副本」,用来做高可用; cfargotunnel.com这个子域名只为同一个 Cloudflare 账号里的 DNS 记录转发流量,别人就算知道了你的 UUID,也没法在他的账号里借用你的隧道。
2. 先花一分钟试试 Quick Tunnel
我的判断是:先用 Quick Tunnel 感受一下,再决定要不要正式配置。它不需要 Cloudflare 账号,也不需要域名。
# 本地先起一个服务,比如在当前目录开个静态服务器
python3 -m http.server 8080
# 另开一个终端
cloudflared tunnel --url http://localhost:8080
# 终端里会打印一个随机的 https://xxxx.trycloudflare.com 地址把这个地址发给朋友,他就能打开你电脑上的页面了,哈哈,第一次用确实有点神奇。
但它只适合测试,官方写明了几条限制:
- 每次启动地址都会变,进程一停就失效,没有可用性保证;
- 单个 Quick Tunnel 最多 200 个同时进行中的请求,超出返回
429; - 不支持 SSE(Server-Sent Events)。
还要注意,拿到链接的任何人都能访问。较新版本的 cloudflared 加了 --allowed-mail 参数,可以要求访客先用邮箱收一次性验证码,比如 --allowed-mail '*@example.com'。我自己的版本是 2026.10.1,用 cloudflared tunnel --help 能看到这个参数,老版本没有的话先升级。
3. 正式隧道:后台管还是命令行管
正式隧道的前提是:域名已经按上一篇的方法接入了 Cloudflare。然后有两种管理方式:
- 远程管理:在后台 Networking 下的 Tunnels 页面创建,复制它给的安装命令,在机器上运行;路由(域名映射到哪个本地服务)也在后台的 Routes 里加。机器上只需要一个 token;
- 本地管理:全程命令行,路由写在本机的
config.yml里。
我的建议:只想快点跑起来,选后台;想把配置纳入 Git、批量部署,选命令行。后台方式机器上只跑一条命令:
sudo cloudflared service install <你的TUNNEL_TOKEN>如果机器上习惯用 Docker,官方也给了容器的跑法:
docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token <你的TUNNEL_TOKEN>这个 token 等于隧道的钥匙,谁拿到它就能用你的隧道接流量,所以别贴到公开仓库里,也别截图发群。下面重点讲命令行方式,因为它能把每一步看得清清楚楚。
4. 命令行六步走完

第一步,安装。 Debian/Ubuntu 用官方 apt 源:
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflaredmacOS 一条 brew install cloudflared 就行,Windows 去官方下载页拿 exe。
第二步,登录。 cloudflared tunnel login 会打开浏览器让你登录并选择域名,完成后在 ~/.cloudflared/ 下生成 cert.pem。后面用命令建 DNS 记录全靠它。
第三步,创建隧道。
cloudflared tunnel create home-lab
cloudflared tunnel list输出里会给出隧道的 UUID,以及凭据文件 <UUID>.json 的路径,记下来。
第四步,写配置文件 ~/.cloudflared/config.yml,最简单的版本:
tunnel: <UUID>
credentials-file: /home/me/.cloudflared/<UUID>.json
ingress:
- hostname: nas.example.com
service: http://localhost:5000
- service: http_status:404第五步,建 DNS 记录。
cloudflared tunnel route dns home-lab nas.example.com它会在 Cloudflare 里自动加一条 CNAME,指向 <UUID>.cfargotunnel.com。
第六步,运行并检查。
cloudflared tunnel run home-lab
cloudflared tunnel info home-lab5. 一条隧道挂多个服务:ingress 规则
一台机器上往往不止一个服务。不用建多条隧道,在 ingress 里多写几条规则就行:
ingress:
- hostname: nas.example.com
service: http://localhost:5000
- hostname: blog.example.com
path: ^/api
service: http://localhost:3000
- hostname: blog.example.com
service: http://localhost:8080
- hostname: "*.example.com"
service: hello_world
- service: http_status:404
规则有三个要点:
- 从上往下匹配,命中第一条就停,所以越具体的规则越要往前放;
- 最后一条必须是兜底规则,不能带 hostname 和 path,否则校验直接失败;
path是正则,而且只用来匹配,转发时路径原样保留,不会帮你去掉/api前缀。
每个新的 hostname 别忘了再跑一次 route dns,多个 CNAME 可以指向同一条隧道。写完先别急着重启,用这两条命令自检:
cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://blog.example.com/api/users
我在本机用上面这份配置实际跑了一遍,validate 输出 OK,访问 /api/users 命中的是 Matched rule #1。注意编号从 0 开始,nas 那条才是 #0。我还故意删掉兜底规则试了一下,它会直接报 The last ingress rule must match all URLs,很省心。
6. 做成系统服务,开机自启
手动 run 关掉终端就断了。正式用要装成系统服务:
sudo cloudflared --config /home/me/.cloudflared/config.yml service install
sudo systemctl start cloudflared
systemctl status cloudflared为什么要显式写 --config?官方文档专门提醒过:用 sudo 执行时 $HOME 指向 /root,它会找不到你家目录下的配置文件。以后改了配置,systemctl restart cloudflared 重新加载即可。
重启会有短暂中断。如果服务比较重要,官方推荐用副本来换配置:先让旧进程继续跑,再用新配置启动一个副本,等它完全连上之后再停掉旧的。要提前知道的是,旧进程停掉时,它身上正在进行的长连接,比如 WebSocket 和 SSH,还是会断开,新请求则由新副本接手。
7. 最容易翻车的四个地方
第一,后台显示 Healthy 不等于网站能打开。 官方说得很清楚,Healthy 只代表 cloudflared 连上了 Cloudflare,不检查你的本地服务。打不开就看日志:
cloudflared tail home-lab
# 重点找 connection refused、x509、TLS handshake 这类报错第二,分清公网域名和本地地址。 service 写的是 cloudflared 自己能访问到的地址,不是访客用的公网域名。服务和 cloudflared 在同一台机器上就写 localhost;在局域网另一台机器上,就写它的内网地址,比如 http://192.0.2.10:80。千万别把 service 写成那个指向隧道的公网域名,那样会绕回自己。
第三,源站本身是 HTTPS 时的重定向循环。 如果本地 Nginx 把 HTTP 全部跳到 HTTPS,而你的 service 写的是 http://localhost:80,就会无限跳转。正确做法是改成 HTTPS,并告诉它证书对应的域名:
- hostname: app.example.com
service: https://localhost:443
originRequest:
originServerName: app.example.com官方明确说了,别用切到 Flexible 的办法解决这个问题;noTLSVerify: true 也只能临时排错用,修好就关掉。
第四,隧道停了,DNS 记录还在。 两者是独立的,cloudflared 一停,访客看到的是 Cloudflare 的错误页,而不会自动回落到别的 A 记录。另外,同名的 A 或 AAAA 记录要删掉,否则流量可能根本不走隧道。
8. 别裸奔:给自用服务加一道登录
用自己的域名发布后,这个地址是全网可访问的。像 NAS、后台管理页这种只给自己用的东西,我强烈建议前面加上 Cloudflare Access:在 Zero Trust 里创建一个自托管应用,选好域名,再写一条只允许你自己邮箱的策略。Access 应用默认拒绝,只有匹配 Allow 策略的人才能进。登录方式可以用一次性验证码(One-time PIN),访客输入邮箱,收码登录。
官方的建议顺序是先建 Access 应用,再加隧道路由,免得中间有一段时间是完全公开的。
至于 SSH、RDP 这类非网页协议,客户端那边也要装 cloudflared 才能连,这块内容比较多,以后单独写。
9. 国内用户要注意的事
- 速度先实测。 上一篇说过,免费套餐的节点在中国大陆以外,Tunnel 的流量也走这些节点,延迟不会比直连低;
- 出站要放行 7844 端口。 公司或学校网络限制严格的话,确认 TCP 和 UDP 的 7844 出站能通,否则隧道建不起来;
- 合规问题 Tunnel 不负责。 它只解决「连不进来」,不改变你提供服务的性质。面向公众开放网站,该备案的还得备案。我更推荐拿它做自用工具的远程访问,再配合上一节的 Access。
小结
一句话概括:
Tunnel = 机器主动连出去 + CNAME 指向隧道 + ingress 规则分发到本地服务。
先用 Quick Tunnel 一分钟体验,再用命令行六步建正式隧道;多个服务靠 ingress 规则,改完用 validate 和 rule 自检;自用服务一定加 Access。做到这几点,家里那台小主机就能安安稳稳地从外面访问了。
好啦,就写到这里,喜欢的朋友点个赞,谢谢。
Cloudflare Tunnel 家庭服务器上网套件:没有公网 IP 也能把家里的服务挂到自己的域名:安装、临时隧道、正式隧道、多服务 ingress、systemd 常驻。
查看源码和下载 →