想用 Tailscale 组网,又不想把设备、策略和节点管理交给托管控制端,Headscale 基本是绕不开的选择。
不过先把一个误区说清楚:Headscale 不是“所有流量都经过 VPS 的 VPN 服务”。它负责设备注册、密钥分发、策略和路由协调;两台设备能直连时,真正的数据仍通过加密的 WireGuard 点对点传输。只有直连失败并启用 DERP 中继时,流量才可能经过中继服务器。
可以把它们分成两层:
- Headscale:自己维护的控制面,负责“谁能加入、谁能访问谁”;
- Tailscale 客户端:装在电脑、手机和服务器上,接收控制面配置;
- WireGuard:客户端之间实际承载加密流量的数据面。
如果只是固定三五台机器互联,直接看VPS 搭建 WireGuard 教程会更简单。Headscale 更适合设备较多、需要用户与标签、子网路由或统一策略的家庭实验室和小团队。
代价也很明确:控制服务器宕机时,已有连接不一定立刻中断,但新设备注册、策略更新和节点重新协调会受影响。升级、备份和域名续费都得自己负责。
Headscale 本身资源占用不高,1 核 1GB、10–20GB SSD 就能作为起点。若开启 embedded DERP,中继带宽可能比 CPU 和内存更先成为瓶颈,建议配合VPS 流量监控与超额预警观察。
准备一个独立域名,例如 headscale.example.com,A 记录直接指向 VPS 公网 IP。
如果 DNS 托管在 Cloudflare,必须使用“仅 DNS”灰云。Headscale 控制协议会通过 POST 升级连接,Cloudflare 橙云代理和 Cloudflare Tunnel 不支持这条路径。
需要的端口:
| 端口 | 用途 |
|---|---|
| TCP 22 | SSH 管理,最好限制来源 |
| TCP 80/443 | Caddy 申请证书和 HTTPS |
| UDP 3478 | 仅 embedded DERP 的 STUN |
先按VPS 安全加固清单配置 SSH 和防火墙,别在改 UFW 时把自己锁在门外。
Headscale 目前推荐 Ubuntu 22.04+ 或 Debian 12+ 使用官方 DEB。本文核验时稳定版为 v0.29.2,但安装时仍应从 GitHub API 读取最新稳定版:
sudo apt update
sudo apt install -y curl jq ca-certificates
headscale_version="$(curl -fsSL https://api.github.com/repos/juanfont/headscale/releases/latest | jq -r '.tag_name | ltrimstr("v")')"
headscale_arch="$(dpkg --print-architecture)"
printf 'version=%s arch=%s\n' "$headscale_version" "$headscale_arch"
curl -fL \
-o /tmp/headscale.deb \
"https://github.com/juanfont/headscale/releases/download/v${headscale_version}/headscale_${headscale_version}_linux_${headscale_arch}.deb"
sudo apt install /tmp/headscale.deb
官方包会创建专用用户、示例配置和 systemd 服务。先别急着开放端口,备份当前配置并对照当前版本的完整示例:
sudo cp /etc/headscale/config.yaml /etc/headscale/config.yaml.before-guide
sudo less /usr/share/doc/headscale/examples/config-example.yaml
sudoedit /etc/headscale/config.yaml
不要从旧博客复制一整份 config.yaml。版本升级时字段会变,最稳的是修改包内示例的这些键:
server_url: https://headscale.example.com
listen_addr: 127.0.0.1:8080
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443
trusted_proxies:
- 127.0.0.1/32
- ::1/128
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true
policy:
mode: file
path: /etc/headscale/policy.hujson
dns:
magic_dns: true
base_domain: tail.example.net
dns.base_domain 不能和 server_url 使用同一个域名。listen_addr 保持回环地址,公网只暴露 Caddy。
验证配置再启动:
sudo headscale configtest
sudo systemctl enable --now headscale
sudo systemctl status headscale --no-pager
Caddy 的完整安装过程可以参考VPS Caddy 反向代理指南。安装后编辑 /etc/caddy/Caddyfile:
http://headscale.example.com {
handle /generate_204 {
respond 204
}
handle * {
redir https://{host}{uri}
}
}
headscale.example.com {
reverse_proxy 127.0.0.1:8080 {
header_up True-Client-IP {remote_host}
header_up X-Real-IP {remote_host}
}
}
检查并重载:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -fsS https://headscale.example.com/health
健康接口正常后,再确认 8080 没有对公网监听:
sudo ss -lntup | grep -E ':443|:8080|:3478'
在 Headscale VPS 上创建用户:
sudo headscale users create alice
sudo headscale users list
在客户端安装官方 Tailscale 客户端,然后指定自定义控制服务器:
sudo tailscale up --login-server=https://headscale.example.com
浏览器会显示注册说明和 Auth ID。回到 Headscale VPS 批准:
sudo headscale auth register --user alice --auth-id AUTH_ID
sudo headscale nodes list
自动化接入可生成一次性预认证 Key:
sudo headscale preauthkeys create --user USER_ID
sudo tailscale up \
--login-server=https://headscale.example.com \
--authkey AUTH_KEY
默认 Key 只能使用一次,有效期一小时。它等同临时凭据,不要写进镜像、仓库或 shell 历史。
手机端不能直接执行命令时,在 Tailscale 应用的账户菜单中选择使用其他服务器,填入完整的 https://headscale.example.com。不同客户端版本的入口名称可能略有变化,但控制服务器地址必须和 server_url 完全一致。完成注册后,立即在服务端核对节点名称、用户和最近在线时间;遇到淘汰或丢失的设备,应先从节点列表确认标识再删除,不能只靠修改 Grants 代替设备撤销。
Headscale 没有加载策略时,节点默认可以互相访问。先创建拒绝全部的 /etc/headscale/policy.hujson:
{
"grants": []
}
确认策略加载后,再按需要增加权限。例如 Alice 的个人设备互通,并允许她访问带 tag:server 的服务器 22 和 443 端口:
{
"tagOwners": {
"tag:server": ["alice@"]
},
"grants": [
{
"src": ["alice@"],
"dst": ["autogroup:self"],
"ip": ["*"]
},
{
"src": ["alice@"],
"dst": ["tag:server"],
"ip": ["22,443"]
}
]
}
重载并看解析日志:
sudo systemctl reload headscale
sudo journalctl -u headscale -n 100 --no-pager
策略写错时不要反复重启。先恢复上一版文件,确认日志没有解析错误,再继续改。
子网路由让 Tailnet 设备访问无法安装 Tailscale 的局域网设备;Exit Node 则把客户端的互联网流量从指定节点转发出去。两者都需要转发节点主动声明,再由 Headscale 批准。
转发节点先开启 IP forwarding:
printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' |
sudo tee /etc/sysctl.d/99-tailscale.conf
sudo sysctl --system
声明局域网路由并批准:
sudo tailscale set --advertise-routes=192.168.50.0/24
sudo headscale nodes list-routes
sudo headscale nodes approve-routes \
--identifier NODE_ID \
--routes 192.168.50.0/24
客户端接受路由:
sudo tailscale set --accept-routes
Exit Node 的流程类似:
sudo tailscale set --advertise-exit-node
sudo headscale nodes list-routes
sudo headscale nodes approve-routes \
--identifier NODE_ID \
--routes 0.0.0.0/0
sudo tailscale set --exit-node EXIT_NODE_NAME
批准路由只代表它可以被使用,Grants 仍应限制哪些用户能访问对应网段或互联网。
设备会先尝试 UDP 直连。处在对称 NAT、严格防火墙或复杂运营商网络后面时,才可能退回 DERP。
Headscale 默认不启用内置 DERP,也会继续向客户端提供 Tailscale 的公共 DERP 列表。若你确实要自建中继,在 config.yaml 的 derp.server 中启用它,保留 verify_clients: true,填写 VPS 的真实公网 IP,并开放 UDP 3478。
不要一上来就清空公共 DERP urls。只剩一台自建 DERP 会形成单点故障,而且中继速度受 VPS 带宽和线路限制。
检查连接:
tailscale ping PEER_NAME
tailscale netcheck
tailscale debug derp-map
tailscale debug derp headscale
tailscale ping 显示 direct 才是点对点连接;显示 via DERP 不代表加密失效,只是流量多绕了一跳。
curl -Iv https://headscale.example.com/health
sudo journalctl -u headscale -n 200 --no-pager
sudo journalctl -u caddy -n 100 --no-pager
重点检查域名是否仍开着 Cloudflare 橙云、系统时间是否正确、Caddy 证书是否成功,以及 server_url 是否写成最终 HTTPS 地址。
sudo headscale nodes list
tailscale status
sudo systemctl status tailscaled --no-pager
控制端在线但客户端离线,多半是客户端服务、DNS、代理或防火墙问题,可对照VPS DNS 解析排查清单继续查。
先看 Headscale 是否成功加载 Grants,再核对用户、标签、目标端口和路由是否批准。不要为了“先跑通”把策略改回永久 allow-all。
要备份的核心是 SQLite、Noise/DERP 私钥、配置、策略和 Caddyfile。最稳的简单做法是短暂停止控制服务再打包:
backup_date="$(date +%F)"
sudo systemctl stop headscale
sudo tar -C / -czf "/root/headscale-backup-${backup_date}.tar.gz" \
etc/headscale \
var/lib/headscale \
etc/caddy/Caddyfile
sudo systemctl start headscale
sudo tar -tzf "/root/headscale-backup-${backup_date}.tar.gz" | head
curl -fsS https://headscale.example.com/health
sudo headscale nodes list
这会造成短暂控制面中断,已有直连通常还能继续,但不要在设备批量注册时执行。正式升级前再做一次VPS 备份恢复演练。
升级顺序:
- 记录当前
headscale version; - 备份并验证压缩包;
- 下载新的官方 DEB;
- 对照新版本
config-example.yaml; - 执行
headscale configtest; - 重启后检查 health、节点列表、Grants、路由和一次真实 peer ping。
升级失败就安装原版本 DEB,并恢复同一次备份中的数据库和配置,别在坏状态上连续跨版本尝试。
网页 health 返回成功,只能证明控制服务可达,不代表整张私有网络已经可用。至少从一个外部网络完成以下检查:
- 新设备能通过 HTTPS 注册,并在
headscale nodes list中归属正确用户; - 两台允许互访的设备可以按主机名解析并连接,未授权端口确实被拒绝;
tailscale ping能区分直连和 DERP,中继情况下也能稳定完成传输;- 子网路由客户端能访问目标局域网地址,关闭
--accept-routes后访问随之消失; - 若启用 Exit Node,访问公网时出口 IP 改变,取消选择后恢复本地出口;
- 重启 Headscale 和 Caddy 后节点、策略和路由仍存在;
- 在另一台机器上解压备份,确认数据库、策略和私钥文件确实包含在内。
最后给域名证书续期、磁盘空间和服务异常配置监控。控制面通常很安静,真正危险的是长期无人注意的证书失效、磁盘写满和备份不可恢复。
- 想省心、设备不多:直接用 Tailscale 托管控制面;
- 想自己掌握设备和策略,并能承担维护:用 Headscale;
- 只有少量固定节点,不需要用户、标签和路由管理:用原生 WireGuard。
Headscale 真正的价值不是“免费 Tailscale”,而是控制权。把 DNS、Grants、备份和升级也管住后,这份控制权才不会变成新的单点故障。
