在 Android 设备上运行 Cloudflare Tunnel

抽屉里那台退役手机,屏幕有点花,但电池还能撑一整天,扔了可惜。我家宽带没有公网 IP,也不想去路由器后台折腾端口映射,但又想给跑在局域网里的几个小服务(一个 Web 面板、一个 Termux 里的 API)开个公网入口。Cloudflare Tunnel 正好对症:设备主动反向连到 Cloudflare 边缘节点,外部流量由边缘转发进来,全程不需要公网 IP、不用开任何端口。于是这台旧手机就被我改造成了一个自带 HTTPS、还能套 Zero Trust 访问控制的轻量内网入口。

下面是我实际走通的两条路,以及把它从”能跑”变成”挂着不掉线”的过程里踩到的坑。

先想清楚走哪条路

动手前我纠结过一阵:到底是在 Termux 里直接跑,还是丢进 proot Debian。后来发现判断标准其实只有一条——这台机器上还要不要跑别的 Linux 工具。

方案什么时候选它复杂度资源占用
Termux + Token只想把本地端口透出去
proot Debian + cert同一套环境里还跑着别的 Debian 服务

我那台旧手机只干透传端口一件事,所以主力是 Token 方案;proot 那套是后来在另一台当开发机用的设备上才用上的。两条都记在这儿。

开工前先把这几样备齐:

  1. 一个已经托管在 Cloudflare 的域名(NS 必须切到 Cloudflare);

  2. GitHub Releases 装的 Termux——别用 Google Play 版,那个早停更了,我一开始就是踩在这上面,新指令各种缺;

  3. 进 Termux 第一件事更新源:

    Terminal window
    pkg update && pkg upgrade

方案 A:Termux + Token(我的主力)

装 cloudflared

省心的是 Termux 官方源直接收录了 cloudflared,不用自己找包:

Terminal window
pkg install cloudflared
cloudflared --version # 出版本号就算装好了

在控制台开隧道

Zero Trust 控制台

  1. Networks → Tunnels → Create a tunnel,连接器选 Cloudflared
  2. 起个名字,下一步会给一整条安装命令——我只需要末尾那串 token,复制出来;
  3. 切到 Public Hostname,把要暴露的子域名指到 http://localhost:<本地端口>,比如 home.example.comhttp://localhost:8080
  4. 保存,回 Termux。

起隧道

Terminal window
cloudflared tunnel --edge-ip-version auto --protocol http2 run --token <粘贴你的 Token>

几秒后刷出 Registered tunnel connection 之类的日志,控制台里隧道转成绿色的 HEALTHY,浏览器打开子域名就通了。

这里我特意加了 --protocol http2。默认走 QUIC,但我家这边网络一跑 QUIC 就时断时续——后面”踩坑”那节细说。网络环境好的话可以去掉,让它走默认。

方案 B:proot Debian + cert 登录

如果机器上已经有一套 proot Debian 在跑别的东西,让 cloudflared 跟它们共用同一个 rootfs 会更顺手。

装 proot Debian

Terminal window
pkg install proot-distro
proot-distro install debian
proot-distro login debian

进容器后补上基础工具:

Terminal window
apt update && apt install -y wget curl

装 cloudflared

Termux + proot 的设备绝大多数是 ARM64:

Terminal window
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
dpkg -i cloudflared-linux-arm64.deb

登录并建隧道

Terminal window
cloudflared tunnel login # 把日志里的 URL 复制到系统浏览器授权
cloudflared tunnel create my-tunnel
cloudflared tunnel list # 记下 UUID

写配置

配置写在 ~/.cloudflared/config.yml——注意目录是 .cloudflared,结尾多一个 d,我手抖少打过一次,排查了半天才发现:

tunnel: <Tunnel-UUID>
credentials-file: /root/.cloudflared/<Tunnel-UUID>.json
ingress:
- hostname: home.example.com
service: http://localhost:8080
- service: http_status:404 # 这条兜底不能省,否则 cloudflared 直接拒绝启动

绑 DNS 再起

Terminal window
cloudflared tunnel route dns my-tunnel home.example.com
cloudflared tunnel run my-tunnel

要管多条隧道,就给每条单独写一份 yaml,启动时用 --config /path/to/config.yml 指定。

真正费时间的是让它”挂住”

跑通只花了十分钟,后面熄屏几分钟就断流这事,才是真把我折腾够呛。Android 回收后台进程非常激进,直接 & 扔后台用不了多久就被杀。两步才稳住:

第一步,抓住唤醒锁,不让系统休眠 Termux:

Terminal window
termux-wake-lock

不用了再 termux-wake-unlock 放掉。生效后通知栏会一直挂着提示,这个提示就是我判断锁有没有拿到的依据。

第二步,交给 termux-services 托管,进程崩了能自动拉起:

Terminal window
pkg install termux-services
# 装完重启一次 Termux,让 sv 环境加载进来
mkdir -p $PREFIX/var/service/cloudflared/log
cat > $PREFIX/var/service/cloudflared/run <<'EOF'
#!/data/data/com.termux/files/usr/bin/sh
exec cloudflared tunnel --protocol http2 run --token YOUR_TOKEN 2>&1
EOF
chmod +x $PREFIX/var/service/cloudflared/run
sv-enable cloudflared
sv up cloudflared
sv status cloudflared # 看到 run: cloudflared 就正常了

日志落在 $PREFIX/var/service/cloudflared/log/main/current,出问题第一时间翻这里。

光做这两步还不够。最后我还去系统设置里把 Termux 塞进了厂商的「电池白名单 / 自启动白名单 / 受保护应用」——三件套配齐,这台旧手机才算真正能长期挂着不掉。

我踩过的坑

proot 里第一次跑就甩了我一脸 x509: certificate signed by unknown authority 原因是 proot Debian 默认证书库不全。补上就好:

Terminal window
apt install -y ca-certificates && update-ca-certificates

隧道在面板上一直 DOWN,日志却说早连上了。 我盯着面板以为没连成功,白折腾。其实是仪表板缓存延迟,等个 30 秒就转绿。要是还不对,回头检查 Public Hostname 里的 service URL 填没填对,再确认本地端口确实在监听(ss -ltnp)。

熄屏几分钟就断——这个最坑,因为不熄屏时一切正常,容易误判成网络问题。 根因要么是没拿到唤醒锁,要么被厂商系统强杀。先确认 termux-wake-lock 生效(看通知栏那条常驻提示在不在),再去系统设置给 Termux 解除后台限制。

QUIC 死活建不了连。 显式加 --protocol http2 切回 HTTP/2 就好。部分国内 ISP 对 UDP/443 有干扰,默认的 QUIC 会卡在握手。我前面命令里一直带着这个参数,就是为它。

参考