技术指南

Headscale 自托管组网:验收控制面与 DERP 回退

把 Headscale 部署成可恢复的控制面:固定并验证版本,约束注册与策略,证明客户端路径,再决定私有 DERP 是否有据可用。

Headscale 是控制面,不是带宽捷径。替换托管控制面前,先写清谁能注册设备、故障时哪些路径必须可用、策略和密钥如何恢复,以及客户端兼容性如何验证。只有路径证据表明中继位置确实是瓶颈时,才评估私有 DERP。

来源横向介绍组网工具,本文收窄为可验收的运维契约

归档来源把 Tailscale、Headscale、ZeroTier、NetBird 和原生 WireGuard,以及注册、子网路由、出口节点、中继和产品排名揉成一条路径。它区分直连与中继这一点有价值,但包含过时的 Headscale 配置键、直接覆盖系统文件、未固定版本的安装命令,并默认本地 DERP 会改善困难线路。本文保留控制面与数据路径的区分,按当前 Headscale 和 Tailscale 文档重写。

为控制权自托管,不要预设会更快

要求托管控制面Headscale
运维责任供应商负责协调服务与可用性自行负责 HTTPS、数据库、私钥、策略、升级与恢复
功能边界按产品当前功能和支持范围使用面向自托管和适度规模、持续变化的兼容子集
身份管理使用供应商支持的身份和管理功能本地审批、预授权密钥或自有 OIDC
流量路径按网络条件直连、peer relay 或 DERP客户端路径类型相同,自托管不会强制流量经过 Headscale

在部署前写出拒绝条件:客户端不支持、443 不可用、没有独立备份、身份依赖无法演练,或恢复本身依赖该 tailnet,都应先保留现有控制面。

固定一个官方版本及其匹配配置

截至 2026-08-17,官方最新版本为 v0.29.3。官方 Debian/Ubuntu 路径支持 Debian 12+ 与 Ubuntu 22.04+;DEB 会创建服务账号、默认配置和 systemd 单元。不要把本文版本直接写进永久自动化,每次部署都重新核对发布页。

保存发布 URL、校验输出、架构和安装版本。同一受损渠道提供的校验和只能证明一致性,不能独立证明来源可信。
VERSION=0.29.3
ARCH=$(dpkg --print-architecture)
BASE=https://github.com/juanfont/headscale/releases/download/v${VERSION}

curl --fail --show-error --location --remote-name \
  "${BASE}/checksums.txt"
curl --fail --show-error --location --remote-name \
  "${BASE}/headscale_${VERSION}_linux_${ARCH}.deb"
sha256sum --check --ignore-missing checksums.txt

sudo apt install "./headscale_${VERSION}_linux_${ARCH}.deb"
headscale version

从已安装版本的示例生成候选配置

当前 v0.29 使用 prefixes.v4/v6 与 database.type/sqlite.path;不要把旧版 ip_prefixes、db_type 或 db_path 示例贴进新版本。
sudo install -d -m 0750 /etc/headscale/candidates
sudo cp /usr/share/doc/headscale/examples/config-example.yaml \
  /etc/headscale/candidates/config-${VERSION}.yaml

# Edit the candidate, not the active file.
sudoedit /etc/headscale/candidates/config-${VERSION}.yaml
sudo headscale -c /etc/headscale/candidates/config-${VERSION}.yaml configtest
  • server_url 固定为客户端长期使用的公开 HTTPS 地址。
  • 主监听与 metrics/debug 分离,后者保持回环或关闭。
  • 地址段只使用 100.64.0.0/10 与 fd7a:115c:a1e0::/48 的受支持子集。
  • 除非已有约束明确要求 PostgreSQL,否则保留 SQLite;项目推荐 SQLite,PostgreSQL 处于维护模式。
  • 把 Noise 密钥、DERP 密钥(如启用)、策略和数据库纳入受保护备份。

只开放所选功能真正需要的接口

端口何时公开边界
TCP 443客户端访问必需Headscale 的可信 HTTPS 入口,必须从外部客户端验证
TCP 80仅内置 HTTP-01 ACME使用其他证书路径时无需公开
UDP 3478仅启用内置 DERP/STUNSTUN 辅助发现,不是控制 API
TCP 50443仅远程 gRPC 管理本地 CLI 足够时保持关闭
TCP 9090默认不公开指标和调试数据保持私有

Headscale 要求公开 IP 和 443/HTTPS;双栈是建议而非硬条件。如果由其他层终止 TLS,迁移前必须测试完整代理链和 Tailscale 控制协议。浏览器返回 200 不能证明客户端能注册或保持 map 流。

先验证拒绝优先的策略,再注册重要节点

没有加载策略时,节点之间默认可以自由通信;空策略对象也表示 allow-all,只有 grants 空数组才表示 deny-all。先写能支持一个明确客户端到服务流的最小授权,验证文件并观察 reload 日志,再扩展权限。

身份和标签都是授权输入;使用这个示意结构前,按当前 Headscale 文档核对策略语法和身份映射。
{
  "groups": {
    "group:operators": ["operator@"]
  },
  "grants": [
    {
      "src": ["group:operators"],
      "dst": ["tag:server"],
      "ip": ["tcp:22", "tcp:443"]
    }
  ]
}
语法有效的策略仍可能授权错误路径;必须用真实节点各测一个允许流和拒绝流。
sudo headscale policy check --file /etc/headscale/policy.hujson
sudo systemctl reload headscale
sudo journalctl -u headscale --since '-2 minutes' --no-pager

让注册凭据短时、可归属且可撤销

交互注册需要管理员批准 Auth ID。自动化注册使用当前默认的一次性、一小时有效预授权密钥。不要把打印出的密钥写入 shell 历史、工单、镜像或 CI 日志;临近注册时再创建,并核对节点所有者、标签和过期时间。

无人值守服务节点只有在标签所有权和 grants 已定义后,才通过预授权密钥下发标签。
headscale users create operator
headscale users list

# Use the numeric user ID from the list. The command prints a secret.
headscale preauthkeys create --user <USER_ID>

# Run on the candidate node without recording AUTH_KEY in shared logs.
sudo tailscale up \
  --login-server=https://headscale.example.com \
  --authkey=<AUTH_KEY>

headscale nodes list

分别证明控制面健康、授权和数据路径

保存两端、接入网络、UTC 时间、客户端版本和完整输出。tailscale ping 是路径证据,真实 TCP 检查才是应用可达性。
curl --fail --silent --show-error \
  https://headscale.example.com/health
headscale nodes list

# On each client pair under test:
tailscale status --json > status.json
tailscale netcheck
tailscale ping <peer-name>
nc -vz <peer-tailnet-ip> 443

连接可能先经 DERP,随后完成 UDP 直连协商。当前客户端会在 status 和 ping 中报告 direct、peer-relay 或 relay。要从每个关键网络重复测量;一次直连不能证明回程、另一接入网、IPv6、DNS 或策略都没问题。

测量回退路径后再添加内置 DERP

Headscale 默认关闭内置 DERP。启用后会把自建中继加入 map,需要公开 HTTPS 和 UDP 3478/STUN,且客户端校验默认开启。只有当部署位置对两端都更可达、重复数据也显示中继是瓶颈时,它才可能改善路径;它修不了控制面、DNS、策略或过载 VPS。

替换为候选服务器的真实公网地址。首次上线保留默认 DERP map,不让一个私有中继成为唯一回退。
derp:
  server:
    enabled: true
    verify_clients: true
    ipv4: 198.51.100.10
    ipv6: 2001:db8::10
  urls:
    - https://controlplane.tailscale.com/derpmap/default
重复比较变更前后的路径、延迟、丢包和应用吞吐。Headscale 只把内置 DERP 定位为连通性辅助,没有吞吐优化。
tailscale debug derp-map > derp-map.json
tailscale debug derp headscale
tailscale netcheck
tailscale ping <peer-name>

备份让 tailnet 可被识别的状态

  1. 停止 Headscale,或使用 SQLite 一致性备份方式后再复制数据库。
  2. 把 /etc/headscale 与 /var/lib/headscale 当作秘密保护,它们可能含配置、策略、私钥和节点状态。
  3. 加密后把备份移出 Headscale VPS,记录校验和与保留期限。
  4. 用同一版本恢复到隔离候选,验证配置、用户/节点和策略后再切 DNS。
  5. 在回滚窗口保留旧控制面地址和安装包。

逐个稳定系列升级,并演练回滚

Headscale 要求按稳定系列顺序升级,并建议每个系列使用最新补丁。每次先读发布说明、停服务、备份配置和数据、对比新版本示例、运行 configtest,再启动候选并重复健康、注册、策略和路径检查。数据库迁移意味着只降级程序包并不构成完整回滚,必须能恢复兼容状态。

隔离演练验证控制面机制,不代表真实 tailnet

VPScope 在 0700 临时目录下载官方 v0.29.3 amd64 DEB 和 checksums,SHA-256 匹配后只解包不安装;二进制报告 v0.29.3,按版本示例改为临时路径和回环端口。configtest 接受候选,并以退出码 1 拒绝 malformed prefix。

解包程序只监听回环,/health 返回 {status: pass},SQLite 创建并列出一个临时用户,SIGTERM 后干净退出,三个监听端口关闭且临时目录清理。演练没有改系统包、防火墙、DNS、服务、Tailscale 客户端、外部端口、真实节点、策略决定或 DERP 路径;它只证明候选校验、健康检查、本地管理和清理。

只有恢复路径独立时才验收 Headscale

  • 自托管理由写成控制权或集成要求,而不是预设提速或隐私宣传。
  • 保存官方版本、架构、校验和、二进制版本和同标签示例配置。
  • 活动文件改变前,候选配置和策略都通过各自验证器。
  • 公开 443、可选 ACME/DERP 端口及私有 metrics/gRPC 与所选功能一致。
  • 注册使用可归属用户和短时秘密,节点所有者、标签与过期时间已复核。
  • 从每个必需客户端平台双向测试允许和拒绝的应用流。
  • 改中继前,用 status、netcheck 和 ping 区分 direct、peer-relay 与 DERP。
  • 在证明私有中继冗余和故障行为前保留默认 DERP 回退。
  • 配置、数据库、私钥和策略能在生产 VPS 外恢复。
  • 升级和回滚覆盖数据库迁移边界,并逐个稳定系列推进。
  • 控制台、DNS 恢复、备份和回滚不只依赖待修复的 tailnet。

返回知识库