自从有一次要远程展示本地开发页面,我在茫茫多的工具里翻出了 Cloudflare 官方的 cloudflared tunnel,从那以后,只要能不碰公网 IP 的方案,我基本都用它。这个官方命令行工具背后就是 Cloudflare Tunnel,它解决的最大痛点是:你不需要公网 IP、不需要路由器端口映射、不需要一台有公网地址的机器做中转,就能把本地服务安全地暴露到公网上。这篇文章我就拿自己的真实经历来说说 cloudflared tunnel 怎么从零上手、核心配置文件怎么看、常见坑怎么排,以及它到底适合什么样的人。
1. 为什么是 cloudflared tunnel:从问题到方案
1.1 现有方案都卡在哪
在没有接触 Cloudflare Tunnel 之前,我需要把本地服务暴露到外网时,试过好几种路子,总结下来各有各的难受。
第一种是路由器端口映射。前提是宽带运营商给你公网 IP,现在很多家庭宽带其实是大内网地址,路由器上看到的 IP 和公网出口 IP 根本对不上,映射了也访问不了。就算有公网 IP,443、80 这些常用端口经常被运营商封掉,换高位端口又得让访问的人记住一串奇怪的数字,体验很差。
第二种是内网穿透工具,比如 ngrok。免费版问题不少:域名随机、每次重启变化、连接数有限制、速度不稳。你要是把服务挂起来做长期演示,得一直开着客户端,断线了还得重新拿新地址,很折腾。
第三种是用 frp 自建中转。这套方案本质上还是需要一台公网服务器,数据从用户到你的服务器,再从服务器转发到内网机器,中间多了个流量网关。服务器带宽要钱、TLS 配置要自己搞、安全补丁要自己打,维护成本并不低。
cloudflared tunnel 的思路完全不同。它不用你开放入站端口,而是从你的机器主动发起一条出站连接,连接到 Cloudflare 的边缘网络。用户访问的时候,请求先到 Cloudflare,再由 Cloudflare 顺着你已经建好的这条隧道转发到你本地的服务上。Cloudflare 官方的东西,全球节点都替你摆好了,你要做的只是跑一个客户端进程。
1.2 cloudflared tunnel 的工作原理
要理解 cloudflared tunnel,最关键的是记住一句话:入站请求变成了出站链接。
传统方案里,用户请求要直接连到你服务器上的某个端口,所以你必须有一个公网可达的入口。而 cloudflared tunnel 的模式是:你本地的 cloudflared 进程主动连到 Cloudflare 边缘,建立一条加密的长连接,然后把这条连接注册到一个隧道 ID 下。之后你域名解析到 Cloudflare,Cloudflare 收到访问请求后,发现这个域名对应的是一条隧道,就把请求通过这条已经建好的长连接转发给你的本地服务。
打个生活化的比方。传统暴露方式是你在马路边开了一个店门,顾客可以直接推门进来,但前提是你得有一个临街的位置,也就是公网 IP 和端口。cloudflared tunnel 则像是你在巷子里开了个店,但专门挖了一条从主干道到店里的地下通道,顾客从主干道入口进来,通过通道直达你的店里。你不需要临街铺面,因为通道是你自己从店里往主干道修的。
这个机制带来的好处非常实际:你的本地机器不需要公网 IP,不需要在路由器上做任何端口映射,甚至机器在多层 NAT 后面也能用。因为连接是主动外发的,所以也没有“运营商封锁入站端口”的问题。同时,用户访问的所有流量都会经过 Cloudflare 的边缘网络,等于天然挂了一层 CDN 和 DDoS 防护。
1.3 适用场景与边界
我在实际使用中觉得,这些场景非常适合 cloudflared tunnel:
- 本地开发联调:把 localhost 上的页面临时分享给同事或客户看效果。
- 家庭服务器 / NAS:把家里的 Jellyfin、Home Assistant、博客等服务安全暴露出去。
- 无公网 IP 的云主机:有些便宜 VPS 没有弹性 IP,或者分配的是内网地址,隧道是最省事的方案。
- 隐藏源站 IP 的生产环境:服务器不直接对公网开放 80/443,出站连到 Cloudflare,能有效降低源站被扫描和攻击的风险。
- GitHub Actions / Codespaces 临时预览:云端开发环境里生成的端口,也能用 cloudflared 快速变成一个公网地址。
但也有不适合的场景。如果你要长时间传输大量数据,比如做网盘直传、在线剪辑,Cloudflare 免费版对流量和连接数有限制,热点文件的传输体验也会打折扣。对延迟极敏感的业务,比如实时游戏对战,隧道多一跳 Cloudflare 边缘,延迟上会有一点损耗。另外,如果你的需求是“必须固定的出口 IP 访问外部数据库白名单”,那隧道出口是 Cloudflare 的随机边缘节点,这个场景需要额外设计方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:安装与账号配置
2.1 多平台安装方式
cloudflared 是开源项目,官方代码仓库在 GitHub 的 cloudflare/cloudflared。它提供了各主流平台的二进制包,安装起来不复杂。
Linux 上我一般直接下载 .deb 包安装,以 Debian/Ubuntu 为例:
bash复制curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
如果你用的是 CentOS/RHEL,可以下载 .rpm 包:
bash复制curl -L --output cloudflared.rpm https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm
sudo rpm -i cloudflared.rpm
macOS 用户直接走 Homebrew:
bash复制brew install cloudflared
Windows 用户去 GitHub Releases 页面下载 cloudflared-windows-amd64.exe,把它放到一个固定目录,然后手动加入 PATH。Docker 用户可以直接用官方镜像,比如:
bash复制docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run <隧道名>
安装完成之后,先验证一下版本:
bash复制cloudflared --version
看到版本号输出基本就说明环境没问题。
2.2 登录授权:让本地工具获得域名权限
正式隧道(非 quick tunnel)需要先把域名托管到 Cloudflare,并且让 cloudflared 拿到操作权限。
第一步,执行:
bash复制cloudflared tunnel login
这个命令会输出一个授权链接,用浏览器打开,选择你要托管隧道的域名,完成授权。成功后,命令会在 ~/.cloudflared/ 目录下生成一个 cert.pem 文件。这个文件非常关键,它代表了你账号的操作凭证,创建隧道、给隧道绑定 DNS 记录都得靠它。所以 cert.pem 一定不要提交到 Git 仓库,也不要在文档里贴出来。
有一点容易混淆的是,cert.pem 是“管理凭证”,而运行隧道时还需要另一个 credentials 文件,就是后面创建隧道时生成的那个 JSON 文件。这两者职责不同,别搞混。
2.3 推荐的目录组织方式
我个人的习惯是,所有 cloudflared 相关文件都放在 ~/.cloudflared/ 下,配置文件命名为 config.yml,隧道凭证文件默认也在那里。每台机器的配置文件都建议指定明确的 tunnel 和 credentials-file,避免混乱。
如果你和我一样,把 cloudflared 装在多台机器上做同一隧道的多连接器,建议统一使用普通用户运行,不要用 root。systemd 托管时,在 service 文件里指定 User= 和配置目录,这样即使未来出现安全问题,影响面也能小一点。
3. 快速上手:5分钟打通第一个隧道
3.1 quick tunnel(临时演示)
如果你只是想临时给朋友看个页面,不想登录账号、不想绑域名,那 quick tunnel 是最快的方式。
假设你本地已经有一个服务跑在 8080 端口,执行:
bash复制cloudflared tunnel --url http://localhost:8080
命令启动后,日志里会输出一个 https://xxx.trycloudflare.com 的随机地址,浏览器打开就能访问到你的本地服务。这个地址是临时生成的,关闭进程后就失效。
我经常用这个功能做远程演示。比如本地跑了个 Vue 开发服务器,我给同事丢一个 trycloudflare.com 链接过去,对方不用安装任何环境,直接点开就能看效果,比打包发 Zip 让他自己跑方便太多了。
不过要提醒一句:quick tunnel 适合临时演示,不适合当正式环境用。它生成的 URL 是随机的,不能自定义域名;默认任何人都能通过这个链接访问你的服务,所以演示完就关掉,别拿它传输敏感数据;长期运行也可能因为会话超时或服务端限制而中断。
3.2 正式隧道创建
如果想让域名固定下来、长期使用,就得创建正式隧道。整个过程分五步。
第一步,登录并授权,前面已经说过了。
第二步,创建隧道:
bash复制cloudflared tunnel create my-tunnel
创建成功后,命令会在 ~/.cloudflared/ 下生成一个 JSON 凭证文件,文件名是隧道 ID,内容包含隧道 ID 和账号信息。同时也会在输出里告诉你隧道 ID,记住它。
第三步,写配置文件。在 ~/.cloudflared/config.yml 中写入类似这样的内容:
yaml复制tunnel: my-tunnel
credentials-file: /home/你的用户名/.cloudflared/<隧道ID>.json
ingress:
- hostname: demo.example.com
service: http://localhost:8080
- service: http_status:404
这里特别说明一下 ingress 的规则:它按从上到下的顺序匹配,第一个命中的规则生效。所以最后一条通常是兜底规则,返回 404,避免有请求落到未配置的域名上。
第四步,把域名解析到隧道上:
bash复制cloudflared tunnel route dns my-tunnel demo.example.com
这个命令会在 Cloudflare 的 DNS 面板上自动创建一个 CNAME 记录,指向 <隧道ID>.cfargotunnel.com,并且默认开启橙色云朵代理模式。
第五步,运行隧道:
bash复制cloudflared tunnel run my-tunnel
看到类似这样的日志,就说明隧道已经跑起来了:
code复制INF Registered tunnel connection conns=1 id=xxxx location=HKG
INF Registered tunnel connection conns=2 id=xxxx location=SIN
这时候访问 https://demo.example.com,请求就会通过 Cloudflare 边缘,经过隧道转发到你的本地服务。
3.3 理解 DNS 路由与接入逻辑
很多人在第一次用的时候会有疑问:为什么 route dns 之后,访问域名不是直接连我的服务器,而是走 Cloudflare?
因为整个链路是:用户浏览器 -> Cloudflare 边缘节点 -> 隧道 -> 你的本地服务。DNS 记录只是告诉 Cloudflare“这个域名对应哪个隧道”,而不是直接解析到你服务器的 IP。这也是为什么源站可以没有公网 IP,因为 Cloudflare 和你的源站根本没有直接建立 TCP 连接,靠的是 cloudflared 主动维护的那条长连接。
还有一个细节:如果你之前有 A 记录直接指向服务器 IP,最好把它删掉或改成 CNAME 到隧道,避免 DNS 记录冲突。开启橙色云朵(Proxied)状态就好了,灰色云朵(DNS only)会导致请求不经过 Cloudflare,隧道根本接不到流量。
4. 配置进阶:从 HTTP 到 TCP
4.1 Ingress 路由详解
ingress 是 cloudflared 配置文件的核心。它支持的 service 类型不止 http://localhost:8080 这一种,常见的有:
| service 写法 | 用途 |
|---|---|
http://localhost:8080 |
转发到本机 HTTP 服务 |
https://localhost:8443 |
转发到本机 HTTPS 服务,可以配合 noTLSVerify |
unix:///var/run/app.sock |
转发到 Unix Socket |
tcp://localhost:22 |
转发 TCP 流量,比如 SSH/RDP |
ssh://localhost:22 |
专为 SSH 设计的简写 |
http_status:404 |
直接返回指定 HTTP 状态码,常用于兜底规则 |
noTLSVerify 是一个常用参数。当你的本地服务是自签名证书时,cloudflared 默认会校验证书并报错,需要在对应规则下加 noTLSVerify: true 跳过校验。注意这只是跳过 cloudflared 到本地服务的校验,实际浏览器到 Cloudflare 之间依然是自动签发的可信证书。
4.2 多服务多域名怎么配
一台服务器上跑多个 Web 服务很常见。比如我家里有一台 NAS,同时跑着媒体服务和智能家居面板,我想让它们分别对应不同的子域名。
配置文件这样写:
yaml复制tunnel: my-tunnel
credentials-file: /home/你的用户名/.cloudflared/<隧道ID>.json
ingress:
- hostname: media.example.com
service: http://localhost:8096
- hostname: home.example.com
service: http://localhost:8123
- service: http_status:404
然后用 route dns 给每个域名分别创建记录:
bash复制cloudflared tunnel route dns my-tunnel media.example.com
cloudflared tunnel route dns my-tunnel home.example.com
一个隧道可以绑定多个域名,不用为每个服务创建单独的隧道,入口统一管理起来更方便。如果想让墙板这样的服务只在内网访问或者加白名单,可以在 Cloudflare Access 里设置策略,后面会讲。
4.3 SSH 场景:TCP 隧道实战
之前我一直用 frp 做内网 SSH,配置麻烦不说,公网服务器还容易被扫描。后来换成 cloudflared 的 TCP 隧道,体验好很多。
配置里增加一条规则:
yaml复制ingress:
- hostname: ssh.example.com
service: ssh://localhost:22
- service: http_status:404
然后创建 DNS 记录:
bash复制cloudflared tunnel route dns my-tunnel ssh.example.com
如果只是这样配置,SSH 服务理论上可以被访问,但显然不安全。建议配合 Cloudflare Access,在 Zero Trust 面板里给 ssh.example.com 配置一条访问策略,只允许指定邮箱或指定 IP 段访问。
客户端连接时,需要先安装 cloudflared,然后通过 ProxyCommand 连接:
bash复制ssh -o ProxyCommand="cloudflared access ssh --hostname ssh.example.com" user@ssh.example.com
cloudflared access ssh 会先通过 Cloudflare Access 的认证,拿到一个短期证书,然后在本地建立一条安全的加密通道连接到 SSH 服务。这样一个公网 SSH 服务就做到了“没有暴露真实端口、访问有身份认证、流量全程加密”,比裸奔的 22 端口安全得多。
4.4 作为系统服务常驻运行
手动在前台跑 cloudflared tunnel run 只适合调试,生产环境肯定要托管成服务。
Linux 上,可以用自带命令安装:
bash复制cloudflared service install
它会创建一个 systemd 服务,并且默认读取 ~/.cloudflared/config.yml。装完之后:
bash复制sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared
macOS 用户可以这样:
bash复制brew services start cloudflared
Windows 用户可以用管理员权限执行 cloudflared service install,服务名称通常是 Cloudflare Tunnel Agent,在服务管理器里能看到。服务化的好处是开机自启、崩溃自动重启,省去很多手动维护的麻烦。
5. 真实场景落地:给本地开发机和家用服务器一个“公网身份证”
5.1 把本地开发预览分享给团队
我在做前后端联调时,经常遇到一个问题:后端接口跑在本地,前端同事要调接口,总不能让他也把整个后端项目跑起来。
有了 cloudflared 就变得很简单。后端服务跑在 localhost:3000,我直接开一条 quick tunnel 或者正式隧道,把地址发给前端同事。团队不用在同一局域网,也不用管对方在哪儿,打开链接就是实时最新的接口状态。
如果在 GitHub Codespaces 这种云端开发环境里,也可以直接在终端安装 cloudflared,然后把 3000 端口暴露出来。很多人甚至把 cloudflared 集成到 GitHub Actions 里,在 CI 跑完自动化测试后,临时生成一个预览地址,方便人工验收 UI 效果。这类场景都是把“本地端口变成公网地址”的典型用法。
5.2 用 Cloudflare Access 给访问加一道锁
把服务暴露到公网,最怕的就是被扫描器盯上。尤其是 NAS、家庭控制面板这类服务,里面基本都是隐私数据,不想让陌生人看到,更不想被搜索引擎收录。
Cloudflare Access 可以解决这个问题,它其实就是 Zero Trust 产品的一部分,免费版对个人开发者来说基本够用。
配置思路很简单:
- 登录 Cloudflare 控制台,进入 Zero Trust。
- 左侧菜单找到 Access -> Applications,创建一个 Self-hosted Application。
- Application Domain 填你要保护的域名,比如
home.example.com。 - 在 Policy 里配置允许访问的规则,比如指定邮箱域名为
@example.com,或者指定某个邮箱。 - 保存后,访问这个域名时,Cloudflare 会先弹出一个登录页,用户验证通过后才能进入你的服务。
我在实际部署中,把博客设为公开,把管理后台和 NAS 面板设为需要邮箱验证。这样隧道还是那个隧道,但访问控制完全由 Cloudflare 边缘完成,本地服务甚至不需要知道有哪些人进来了。这个方案比在本地服务里写权限逻辑要省心很多。
5.3 隐藏源站 IP 的生产部署思路
生产环境如果源站 IP 暴露了,即使套了 CDN,攻击者也可能绕过 CDN 直接打源站。cloudflared tunnel 可以从物理层面避免这个问题,因为源站根本没有对公网开放入站端口。
我的一个 API 服务部署在一台只有内网地址的机器上,外网完全访问不到它的任何端口。它只需要能出网访问 Cloudflare 即可。所有外部请求先进 Cloudflare,Cloudflare 通过隧道把请求转发到这台机器上的服务。哪怕有人用扫描工具全网扫描,也扫不到这台机器的任何服务,因为这台机器根本没有公网入口。
如果服务和本机都在同一台服务器上,建议把 cloudflared 作为独立服务运行,和业务进程分离。业务进程只监听 localhost 或内网网卡,cloudflared 进程负责和 Cloudflare 建立连接。这样两边的安全边界更清晰,就算业务服务被攻击,攻击者也不能通过公网直接访问它。
如果一台机器不够,你还可以在另一台机器上跑同一个隧道的连接器,多个连接器同时在线,Cloudflare 会自动做负载均衡和故障切换。我就在家庭带宽和一条备用宽带上各跑了一个连接器,一条线断了,另一条自动接上,对外完全无感。
6. 常见问题与排查实录
6.1 隧道连上了但访问 502/522 怎么办
我见到最多的一个问题就是:隧道状态正常,但访问时浏览器报 502 或者 522。
这类错误通常不是隧道本身的问题,而是 cloudflared 无法连接到源站服务。排查路径从简单到复杂:
- 先在隧道所在机器上确认源站服务真的在监听:
curl http://localhost:8080,如果 curl 都连不上,那问题在源站服务。 - 确认配置里的
service地址写对了。常见错误是把服务绑定在127.0.0.1,但 cloudflared 运行在 Docker 容器里,容器里的localhost是容器自己的回环地址,不是宿主机的端口。这种情况检查容器网络模式。 - 看 cloudflared 的实时日志。运行隧道时加
--loglevel debug,日志里会明确告诉你它尝试连接了哪个地址、报了什么错。 - 确认没有在
ingress规则里配错了 hostname。有时候域名能访问,但请求根本没有命中你配置的规则,而是打到了兜底的 404 上。
我的经验是:先 curl 源站,再看 cloudflared 日志,两步能解决九成问题。
6.2 DNS 不生效 / 一直证书错误
如果你的域名解析到了隧道,但访问时一直提示证书错误或者跳转到奇怪页面,通常和 DNS 记录状态有关。
- 确认 DNS 记录是 CNAME 指向
<隧道ID>.cfargotunnel.com,并且状态是橙色云朵。如果你创建的是灰色云朵(DNS only),请求不会经过 Cloudflare,隧道自然也接不到。 - 确认域名确实在你授权的 Cloudflare 账户下。如果一个域名不在该 Zone 里,
route dns可能会失败,或者创建了 CNAME 但没有生效。 - 证书由 Cloudflare 自动签发,但前提是域名已经成功通过 Cloudflare 代理访问到你的隧道。一般几分钟内证书会自动签发,等待即可。
- 如果你之前用过其他 CDN 或 A 记录,记得清理旧记录,否则可能解析到旧地址上。
一个很典型的错误是 1016 或 1017,这通常表示 Cloudflare 边缘找不到对应的隧道。检查一下 tunnel 配置里的名称或 ID 是否和创建时一致,以及 credentials-file 指向是否正确。新版 cloudflared 可以直接用隧道名称写在 tunnel: 字段里,但我还是习惯写上兼容性更好的隧道 ID。
6.3 日志怎么看,关键排查命令
日常维护 cloudflared,我常用的命令就这几个:
bash复制cloudflared tunnel list
cloudflared tunnel info <隧道名>
cloudflared tunnel ingress validate
cloudflared tunnel run <隧道名> --loglevel debug
tunnel list 能看到当前账户下所有隧道及其状态,tunnel info 显示这条隧道注册的连接器列表。如果部署多连接器,可以通过这里看每个连接器是否在线、分布在哪个地区。
ingress validate 是一个容易被忽略的好命令。改完配置文件后,先跑一下这个命令,它能帮你检查 YAML 语法和 ingress 规则是否合法,避免小错误等到线上才炸出来。
日志里出现 ERR 级别时,我会重点看后面对应的错误码和上下文。比如 ERR error="Unable to reach the origin service" 基本就是源站连接问题,WARN 级别的一般是 DNS 或证书状态异常,可以晚点处理。
6.4 几个必须知道的坑
我踩过几次坑之后,总结了几条必须注意的点。
第一,quick tunnel 的 URL 不是绝对安全的。它只是一个随机链接,拿到链接的人都能访问你的服务。不要用它来展示包含密码、个人信息、支付页面的服务。用完就关进程。
第二,credentials-file 和 cert.pem 千万不能泄露。这两个文件一旦泄露,别人可以尝试接管你的隧道配置,或者在你账户下创建新隧道。我在配置服务器时会给这两个文件加上严格的文件权限,比如 chmod 600。
第三,不要把隧道 ID 直接写在公开的配置示例里。配置文件如果提交到 Git 仓库,记得做脱敏处理,或者用环境变量注入。
第四,升级 cloudflared 时要留意版本兼容性。老版本客户端连接新边缘节点偶尔会有协议问题,建议定期升级到最新稳定版。不过也别紧跟 latest 版本,我一般会滞后一个版本,等社区反馈正常再升。
第五,如果你的源站服务本身也有访问日志,你会发现所有请求都来自 Cloudflare 边缘 IP,而不是真实用户 IP。真实用户 IP 会放在 CF-Connecting-IP 头里。如果业务需要获取用户 IP,记得在反向代理层做处理。
最后再分享一个小技巧。如果你和我一样要维护多个隧道、多台机器,我建议把每个机器的 config.yml 纳入版本管理,但只保留脱敏后的内容,真实的 tunnel 和 credentials-file 路径用 .env 或部署系统注入。这样换机器迁移时,复制配置再重新登录授权就能复现。cloudflared tunnel 用顺了之后,你会慢慢发现它早已不只是“内网穿透”工具,而是一层真正有用的公网入口层。
