作者:Felix (Haro 协作整理)
我现在的私有组网主方案仍然是 ZeroTier。zerotier-one 在 Linux、OpenWrt 等设备上一直比较稳定,所以这次并不是想把整个网络迁移到 Tailscale / Headscale。
引入 Headscale,主要是为了补 iOS。在 zerotier-one 1.16.0 之后,iOS 客户端已经不再支持自建 Planet 部署,这意味着 iPhone、iPad 无法继续顺利接入原有自建 ZeroTier 网络。对我来说,Headscale 更像一个补充入口,而不是替代方案。
另外,Tailscale / Headscale 在子网路由上的配置方式也不算灵活。它更适合按“节点通告路由,控制面批准路由”的方式工作,适合作为规范化入口,但不太适合直接替代已经跑顺的主网络结构。
这次接入 Headscale 的目标很明确:
- 给 iOS 提供可长期使用的接入方式
- 在需要时通过 Exit Node 复用现有出口节点
- 尽量不打乱已经稳定运行的 ZeroTier 主网络
这里的 Exit Node,主要也不是为了获得私网能力,而是为了给 iOS 提供统一出网路径,减少额外维护代理出口节点的成本。
结论
1. iOS 上要开启 MagicDNS
如果 iPhone 需要稳定使用 Exit Node,那么 MagicDNS 需要开启。在我的环境里,如果没有打开这个选项,就会出现“看起来已经连上了 Exit Node,但任何网站都打不开”的情况。这个现象表面像路由或出口问题,实际核心还是 DNS。
2. 私有 Headscale 的登录入口比较隐蔽
Tailscale 客户端默认会走官方控制面。如果要接入私有 Headscale,需要在登录界面右上角打开菜单,选择 Sign in with other,再填写私有控制面的地址:
https://<headscale-domain>
如果直接按默认流程登录,接入的就是官方 Tailscale,而不是自己的 Headscale。
3. 子网路由和 Exit Node 需要显式批准
在客户端上声明路由或 Exit Node,只是完成了通告,并不代表已经生效。
例如通告子网路由:
sudo tailscale up --login-server=https://<headscale-domain> --advertise-routes=192.168.100.0/24
例如同时通告 Exit Node:
sudo tailscale up --login-server=https://<headscale-domain> --advertise-routes=192.168.100.0/24 --advertise-exit-node
然后在 Headscale 侧查看并批准:
headscale nodes list-routes
headscale routes enable --identifier <node-name-or-id> --routes 192.168.100.0/24,0.0.0.0/0,::/0
这一套的逻辑很简单:
- 客户端负责通告
- Headscale 负责批准
如果没有完成批准,路由和 Exit Node 往往只是“看起来配了”,但实际上并不能正常使用。
4. 真正的坑不在 Headscale 本身
因为 HTTPS 我提前已经配好了,所以这次真正麻烦的地方,不在 Headscale 服务端本身,而在几个很容易被忽略的细节上:
- 客户端是不是登录到了私有 Headscale
- iOS 是不是开启了 MagicDNS
- 路由是不是已经在 Headscale 侧真正批准
5. STUN/UDP 端口写错,也会让连接表现异常
这次还有一个比较隐蔽的坑,问题不在 Headscale 配置本身,而在服务器防火墙规则。我原本应该放行的 STUN/UDP 端口是:
63478/udp
但实际防火墙里误配成了:
64378/udp
这个错误不会让整个服务直接完全不可用,但会导致打洞行为异常,表现出来就是连接建立变慢、链路质量不稳定,实际体验会明显变差。如果这时候再叠加其它客户端侧问题,就很容易把现象误判成 Headscale、DERP 或路由配置本身有问题。
所以在排查连接慢、打洞不稳定这类问题时,除了看控制面和客户端配置,也要顺手核对一下服务器侧实际放行的 UDP 端口是否和服务配置完全一致。像这种单个数字写错一位的情况,排查起来往往比服务起不来还更绕。
最后
这次实践最后收敛出来的方案很简单:
- ZeroTier 继续作为主网络
- Headscale 作为 iOS 的补充接入方案
- Exit Node 用来复用现有出口节点
- iOS 上开启 MagicDNS
- 私有 Headscale 通过 Sign in with other 接入
- 子网路由和 Exit Node 在客户端通告后,还要在 Headscale 侧显式批准
对我来说,这套组合的价值不在于替代,而在于用尽量小的改动,把 iOS 接入这块补齐,同时保留现有 ZeroTier 网络已经验证过的稳定性。