使用 Headscale 构建高可用 Homelab 备用 VPN (自托管 Tailscale)
为什么我的 Homelab 需要“备用 VPN”
像许多 Homelab 用户一样,我的主要远程访问方案通过 WireGuard 实现。它快速、优雅且简单。
然而,WireGuard 有一个结构性的弱点:
它依赖于 UDP。
实际上,这在以下情况会成为真正的问题:
- 公共 Wi-Fi(咖啡馆、机场、酒店)
- 企业访客网络
- 具有激进 NAT 或过滤功能的蜂窝热点
我经常遇到以下情况:
- WireGuard 根本无法连接
- 其他 VPN 协议也失败
- 只允许 TCP 443 (HTTPS) 流量
此时,速度已不再重要 —— 连通性才是关键。
因此,我的目标变得非常明确:
设计一个完全自托管、原生的 Homelab VPN,当 UDP 被阻塞时自动回退到 TCP 443。
没有商业 VPN。没有订阅。没有“黑盒”依赖。
为什么不直接使用商业 VPN?
商业 VPN 确实 解决了 TCP 443 问题 —— 但它们引入了新的权衡:
- 经常性成本
- 外部信任依赖
- 无法直接访问我的私有局域网
- 额外的跳转和不可预测的路由
我想要:
- 直接访问我的 家庭网络
- 完全控制路由和安全性
- 我可以理清和记录的基础设施
这自然将我引向了 Tailscale —— 继而转向 Headscale。
Tailscale vs Headscale (简要回顾)
- Tailscale: 托管控制平面 + WireGuard 数据平面
- Headscale: 开源、自托管的控制平面,兼容 Tailscale 客户端
关键见解:
你可以自托管 控制平面 (Headscale)
同时仍受益于 Tailscale 成熟的客户端、NAT 穿透和 DERP 回退。
这提供了:
- 良好网络下的 WireGuard 性能
- 恶劣网络下通过 DERP 自动回退到 TCP 443
- 无供应商锁定
高层架构与设计
(公共或自托管)"]) Router(["ts-router LXC"]) LAN(["家庭局域网: 192.168.10.0/24"]) User -- "WireGuard UDP (直连)" --> Router User -- "UDP 被阻断" --> TS TS -- "TCP 443" --> DERP DERP --> Router Router --> LAN
关键设计选择
- Headscale 作为单一事实来源
- 专用子网路由 (
ts-router) 而不是让现有主机过载 - SNAT/MASQUERADE 避免脆弱的返回路由
- 无出口节点 —— 这是一个 家庭访问 VPN,而不是流量隧道
部署步骤 1:Headscale 控制平面 (LXC)
建议:单独容器、职责单一(不要与 Caddy/业务容器混跑)。Headscale 只是控制面 HTTP 服务,不需要 TUN 设备。
推荐配置:
- OS:Debian 12 或 Ubuntu LTS
- CPU/RAM:1 vCPU / 256MB+
- Network:桥接到 LAN(需可被 Caddy 访问)
配置文件调整
配置文件路径通常为:/etc/headscale/config.yaml。
关键是 server_url 必须是公网 HTTPS 域名,且 listen_addr 使用内网监听交由 Caddy 反代。
示例配置:
1server_url: "https://headscale.example.com"
2listen_addr: "0.0.0.0:8080"
3
4# 不让 headscale 自己处理 TLS
5tls_letsencrypt_hostname: ""
6tls_letsencrypt_cache_dir: ""
Caddy 反向代理 (TLS 终止)
在 Caddy 机器配置:
1headscale.example.com {
2 reverse_proxy <HEADSCALE_LXC_IP>:8080
3}
验证:curl -vk https://headscale.example.com/health 应返回 200 和 {"status":"pass"}。
创建用户
1# 找到 johnny 对应的 ID,例如 1
2headscale users list
3
4# 创建 Pre-auth key 用于路由节点接入
5headscale preauthkeys create --user 1 --expiration 24h
部署步骤 2:数据平面与路由 (ts-router LXC)
这是最重要的部分。ts-router 必须能使用 TUN,否则 tailscaled 无法工作。
LXC 设置重点:
- TUN:✅ 必须开启
- OS:Debian 12 (推荐)
- Network:建议固定 IP
安装并接入 Headscale
1# 安装 Tailscale
2curl -fsSL https://tailscale.com/install.sh | sh
3systemctl enable --now tailscaled
4
5# 接入并通告路由
6sudo tailscale up \
7 --login-server=https://headscale.example.com \
8 --authkey=tskey-xxxxxxxxxxxxxxxx \
9 --advertise-routes=192.168.10.0/24 \
10 --accept-dns=false
接入后,必须在 Headscale 侧批准路由:
1headscale routes list
2# 找到路由 ID
3headscale routes enable --route <ROUTE_ID>
关键配置:开启转发与 SNAT
这是大多数人遇到的“坑”。
- 开启 IPv4 转发:
1echo 'net.ipv4.ip_forward=1' >/etc/sysctl.d/99-tailscale-router.conf
2sysctl -p /etc/sysctl.d/99-tailscale-router.conf
- 配置 SNAT/MASQUERADE:
由于家庭网关通常不知道 100.64.0.0/10 的回程路由,必须使用 SNAT 确保回包正确路由。
1# 临时测试
2iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -o eth0 -j MASQUERADE
3
4# 持久化 (推荐)
5apt install -y iptables-persistent
6# 安装时选择保存规则
一旦 SNAT 就位,内网访问应该立即通畅。
部署步骤 3:客户端接入
macOS / Linux
使用命令行接入,关键是 --accept-routes 以便系统接收内网路由表。
1tailscale up \
2 --login-server=https://headscale.example.com \
3 --accept-routes
Android / iOS
- 修改 Control URL(部分客户端需进入高级设置或多次点击版本号开启)。填写
https://headscale.example.com。 - 使用
tskey或在浏览器登录认证。 - 在客户端设置中开启 "Accept routes"。
验证与测试
1. 基础连通性
在外部网络(或手机 5G)下,尝试 ping 内网 IP(如 192.168.10.1)。
2. 模拟 UDP 封锁 (自动回退测试)
当 UDP 不可用时,Tailscale 会自动通过 DERP服务器(TCP 443)中继。
检查状态:
1tailscale status
2# 如果看到 relay "sea" 等字样,说明正在走中继
tailscale netcheck 也可以显示详细的 UDP/DERP 连接状态。
安全与信任模型
此设置有意避免:
- 直接暴露家庭路由器
- 在局域网打开入站 VPN 端口
- 授予全隧道出口节点访问权限
相反:
- 仅经过身份验证的设备加入 tailnet
- 仅接受批准的子网路由
- 流量范围限于家庭局域网
这与 最小权限 Homelab 哲学 高度一致。
总结
这个项目不是为了追求新奇 —— 而是为了 消除真正的可靠性差距。
通过 Headscale 和专用的路由容器,我们构建了一个系统,它:
- 尽可能使用 WireGuard (UDP) 以获得最佳性能
- 在受限网络下透明回退到 TCP 443 (DERP)
- 保持完全自托管和可审计
最重要的是,它现在很无聊 —— 这正是优秀基础设施应有的样子。