使用 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
  • 无供应商锁定

高层架构与设计

graph TD User(["笔记本 / 手机"]) TS(["Tailscale 客户端"]) DERP(["DERP 中继
(公共或自托管)"]) 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

这是大多数人遇到的“坑”。

  1. 开启 IPv4 转发
1echo 'net.ipv4.ip_forward=1' >/etc/sysctl.d/99-tailscale-router.conf
2sysctl -p /etc/sysctl.d/99-tailscale-router.conf
  1. 配置 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

  1. 修改 Control URL(部分客户端需进入高级设置或多次点击版本号开启)。填写 https://headscale.example.com
  2. 使用 tskey 或在浏览器登录认证。
  3. 在客户端设置中开启 "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)
  • 保持完全自托管和可审计

最重要的是,它现在很无聊 —— 这正是优秀基础设施应有的样子。

comments powered by Disqus

翻译: