Headscale 自托管 Tailscale:VPS 安装与节点接入
在 VPS 上运行自己的 Tailscale 控制服务器:从官方 .deb 安装 Headscale,设置 server_url 并重启服务,然后接入第一个节点。
Headscale 是什么
Headscale 是 Tailscale 控制服务器的自托管实现。因此,负责协调您的私有网络的计算机是您拥有的 VPS。它是一个社区项目,不由 Tailscale Inc. 运营。每台计算机仍运行官方 tailscale 客户端,只需通过一个标志 --login-server 将其指向您的服务器。
控制服务器负责记录哪些设备属于该网络。它会从 100.64.0.0/10 中为每个节点分配地址,分发公钥,并告知节点如何找到彼此。隧道仍使用 WireGuard,并在节点之间直接建立。除非无法建立直接路径、节点回退到中继,否则两台设备之间的流量不会经过 headscale 服务器。自行运行这一协调角色,只会改变由谁持有该角色,不会改变它能够执行的功能。因此,在您仅凭迁移本身将其视为安全性提升之前,值得先了解在这种模型中控制服务器能够或不能访问什么。
Headscale 每个实例提供一个 tailnet(一个 Tailscale 网络)。该项目将其定位为适合个人或小型组织使用。只有三四台设备时,在自有 VPS 上运行普通 WireGuard VPN 所需的软件更少,出问题的环节也更少。等到您不想再为每台新笔记本手动编写一个 [Peer] 配置块时,Headscale 才更有价值。成本通常是人们开始了解这类方案的主要原因,因此在承担服务器运维成本前,建议先阅读托管免费套餐实际包含的内容,因为少量个人设备通常已在其范围内。如果您已经超过该上限,请将费用与按用户和按标签资源计费的付费套餐价格进行比较,因为用户设备数量不限,而带标签的资源有单独的配额和费用。如果您希望使用自托管控制平面,但更倾向于使用自有客户端和 Web 界面来管理对等节点,而不是直接替代 Tailscale,那么在单台 VPS 上部署 NetBird值得考虑。两者都通过 WireGuard 承载网络流量,因此选择主要取决于访问规则的编写方式和中继的工作方式。在安装任一方案前,建议先比较 NetBird 与 Tailscale 在这些方面的差异。如需了解这两种模式的整体差异,请参阅WireGuard 与 Tailscale 的区别。
安装前的准备条件
- 一台运行 Ubuntu 24.04 的 VPS,具有公网 IPv4 地址和 sudo 访问权限。如果服务器是新建的,请先完成新 VPS 上线后的前十分钟。
- 一条指向该地址的 DNS A 记录。本指南使用
headscale.example.com。 - 用于 MagicDNS 的另一个域名或子域名。本指南使用
tailnet.example.net。它不能与server_url中的域名相同。 - 一台用于加入网络的客户端设备,运行 Linux、macOS、Windows、Android 或 iOS。Headscale 0.29.6 要求 Tailscale 客户端版本为 1.80.0 或更高版本。
从官方 .deb 安装 headscale
项目会在其 GitHub releases 页面发布 .deb 软件包。截至 2026 年 10 月 27 日,最新稳定版本为 0.29.6,于 2026 年 10 月 24 日发布。请先检查架构,因为文件名中包含架构信息。
sudo apt update
sudo apt install -y wget
dpkg --print-architecture在普通的 x86 VPS 上,该命令输出 amd64;在 Ampere 或 Graviton 类型的方案上输出 arm64。将结果放入下面的变量中。
HEADSCALE_VERSION="0.29.6"
HEADSCALE_ARCH="amd64"
wget --output-document=headscale.deb \\
"https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
sudo apt install -y ./headscale.deb
headscale version文件名前的 ./ 是必需的。如果缺少它,apt 会在软件源中查找名为 headscale.deb 的软件包,并失败。
该软件包会创建一个 headscale 系统用户,写入默认的 /etc/headscale/config.yaml,安装 systemd 单元,然后启动服务。因此,apt 返回时 headscale 已经在运行,sudo systemctl is-active headscale 会输出 active。不过,全新安装时软件包不会启用该单元;在启用之前,服务在系统重启后不会自动恢复,而下面的重启步骤会完成启用。服务也会使用随软件包提供的配置运行,该配置将 server_url 指向 http://127.0.0.1:8080,而您的任何客户端都无法访问这个地址。当前还没有对外暴露任何内容,因为随软件包提供的 listen_addr 也绑定在回环接口上;但在设置 server_url 并重启服务之前,不要加入任何客户端。接下来的两个步骤会完成这些操作。
配置 server_url,然后再让任何客户端加入
使用 sudo nano /etc/headscale/config.yaml 编辑 /etc/headscale/config.yaml,或使用 sed 应用相同的三项修改。保留原始文件的副本,因为该文件很长且包含大量注释,是后续配置其他设置时最好的参考。
sudo cp /etc/headscale/config.yaml /etc/headscale/config.yaml.orig
sudo sed -i 's|^server_url:.*|server_url: https://headscale.example.com|' /etc/headscale/config.yaml
sudo sed -i 's|^listen_addr:.*|listen_addr: 127.0.0.1:8080|' /etc/headscale/config.yaml
sudo sed -i 's|^ base_domain:.*| base_domain: tailnet.example.net|' /etc/headscale/config.yaml
sudo grep -E '^(server_url|listen_addr):|^ base_domain:' /etc/headscale/config.yamlserver_url 是 headscale 在生成注册 URL 时使用的公网地址。客户端使用 --login-server 配置的控制地址,因此两处都应使用相同的公网名称,并在其中使用 https://,不要使用 127.0.0.1。headscale 文档说明可以使用普通 HTTP,也可以在 443 以外的端口上使用 HTTPS,但强烈建议生产环境在 443 端口上使用 HTTPS。本指南的其余部分也基于此配置。
listen_addr 是进程绑定的地址。将其保留为 loopback。相同服务器上的反向代理负责终止 TLS(传输层安全协议)并将请求转发到该地址,因此服务器外部无需访问 8080 端口。
base_domain 是 MagicDNS 后缀,即节点名称所属的域。它必须是一个不带末尾点号的完全限定域名,并且必须与 server_url 中的域不同,否则两个名称空间会发生冲突。
不要修改数据库部分。默认数据库是位于 /var/lib/headscale/db.sqlite 的 SQLite,所在目录由软件包创建并归其所有。对于这种规模的 tailnet,SQLite 已经足够。
重启 headscale 并确认其正在运行
sudo systemctl restart headscale
sudo systemctl enable headscale
sudo systemctl is-active headscale
sudo systemctl is-enabled headscale
curl -sS -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:8080/healthis-active 输出 active,is-enabled 输出 enabled,而 curl 输出 200。重启是加载修改内容的步骤。headscale 只在启动时读取一次配置,因此软件包启动的进程会继续使用随软件发布的 server_url,直到您重启它。sudo systemctl enable --now headscale 无法完成这项工作:服务已经在运行,因此 --now 启动不会产生任何变化,旧值仍会生效。开机启动由 enable 负责,而且不会自动为您完成:全新安装时,软件包会启动服务但不会启用它,因此请像上面一样运行一次 sudo systemctl enable headscale。
如果 is-active 输出的内容不是 active,或者 curl 没有输出 200,请使用 sudo journalctl -u headscale -n 50 --no-pager 读取日志。此阶段的失败几乎总是由配置文件引起,因为 headscale 会在打开套接字前解析整个文件;缩进错误或必需设置无效,都会在任何端口开始监听前停止进程。修复文件后,再运行 sudo systemctl restart headscale。之后每次修改配置都需要执行相同的重启操作。客户端随后会自动重新连接。如果您不熟悉 systemd 单元,请参阅 使用 systemd 运行您自己的服务和计时器,其中介绍了这里使用的命令。
在 shell 中检查状态文件:
stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.key两行都以 headscale 开头,这是软件包创建的非特权用户。noise_private.key 是服务器向客户端提供的身份标识。请保留它。如果删除它,headscale 会生成新的标识,客户端必须先获取新的服务器密钥才能重新连接;现有注册信息仍保留在数据库中。
在 headscale 前配置 TLS
客户端应通过 HTTPS 访问 server_url。Caddy 是最简便的方案,因为它会自动申请和续期证书。
sudo apt install -y caddy将 /etc/caddy/Caddyfile 替换为 headscale 文档中的配置块:
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 --adapter caddyfile --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl is-active caddy如果文件解析成功,validate 会输出 adapted config to JSON。提示文件尚未格式化只是外观问题。从笔记本电脑执行 curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health 时,也应输出 200。这项检查可以同时验证 DNS、防火墙、证书和代理是否正常协同工作。
下面是最容易让人排查一晚的问题。Tailscale 控制连接使用 HTTP upgrade,通过 POST 而不是 GET 发起,Upgrade 请求头的值为 tailscale-control-protocol。Caddy 无需额外配置即可转发这些内容。nginx 不会自动转发,因此 nginx 前端必须显式转发 Upgrade 和 Connection 请求头。headscale 文档通过 upgrade map 实现这一点:
map $http_upgrade $connection_upgrade {
default keep-alive;
'' close;
}
server {
listen 443 ssl;
server_name headscale.example.com;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_pass http://127.0.0.1:8080;
}
}如果省略这些配置,普通请求仍会成功。这就是为什么 /health 会返回 200,看起来一切正常,但控制连接始终无法建立:新节点无法注册,现有节点也无法重新连接并报告在线状态。如果选择 nginx,Ubuntu 24.04 上使用 nginx 配置 Certbot介绍了证书配置部分。
UFW 中需要开放的端口
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose端口 443 承载所有客户端与 headscale 之间的控制连接。如果之后启用内置 DERP 中继,还需要开放 3478/udp,具体如下文所述。端口 80 用于 ACME(自动证书管理环境)HTTP challenge,以及将请求重定向到 HTTPS。即使关闭端口 80,Caddy 仍可通过端口 443 上的 TLS-ALPN challenge 获取证书;但开放端口 80 后,两种 challenge 都可用,普通的 http:// 请求也会被重定向。
端口 8080 保持关闭。listen_addr 是 127.0.0.1:8080,因此代理通过 loopback 接口访问 headscale,不涉及任何防火墙规则。在 UFW 中开放端口 8080 不会暴露任何服务,因为外部没有进程监听该端口,也不会带来任何好处。请注意,大多数服务商会在控制面板中运行独立于 UFW 的第二层防火墙,因此某个端口即使在服务器上处于开放状态,在网络边界处仍可能处于关闭状态。VPS 上的 UFW 防火墙基础更详细地介绍了规则语法。
创建用户和预认证密钥
sudo headscale users create alice
sudo headscale users listheadscale 命令是客户端。它通过位于 /var/run/headscale/headscale.sock 的 Unix 套接字与运行中的 daemon 通信。该套接字的模式为 0770,所属组为 headscale。这会产生两个结果。服务停止时,该命令会失败,这也是本指南必须按此顺序操作的另一个原因。除非将您自己的账户加入 headscale 组,否则还需要 sudo。
users list 会在每个名称旁边打印 ID。您需要这个数字,因为密钥命令接受数字用户 ID,而不是名称。
sudo headscale preauthkeys create --user 1 --expiration 24h密钥只会显示一次。请立即复制它。预认证密钥只能使用一次,默认有效期为 1 小时;除非另行指定,因此在测试期间设置 --expiration 24h 很有用。添加 --reusable 可创建能为多台计算机注册的密钥。请像保护密码一样保护该密钥,因为持有它的任何人都可以加入您的网络。
使用 --login-server 连接第一个客户端
在要加入 tailnet 的机器上:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://headscale.example.com --auth-key 'hskey-auth-PASTE-YOUR-KEY-HERE'
tailscale status
tailscale ip -4tailscale ip -4 会输出 headscale 分配的地址,例如 100.64.0.1。返回服务器后,sudo headscale nodes list 会显示该节点的 ID、所属用户及在线状态。
将 server_url 中的公共 HTTPS 地址用于 --login-server。客户端会删除控制 URL 末尾的斜杠;不存在要求省略斜杠的精确字符串比较。
之前登录过 Tailscale 托管服务的机器会保留该登录状态。先在该机器上运行 sudo tailscale logout,然后使用 --login-server 运行 tailscale up。
如果省略 --auth-key,客户端会输出一个 URL。打开该 URL,页面会显示本次注册尝试的标识符。然后在服务器上批准该注册:
sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGE这种方式更适合自己的笔记本电脑。对于脚本化场景,预授权密钥更合适,因为不需要人工等待确认。VPS 本身成为节点后,还可以承载其他机器的互联网流量,这就是出口节点配置。不同之处在于,应在服务器上使用 headscale 命令批准已发布的路由,而不是在托管管理控制台中批准。如果你的目标是访问 VPS 后方的私有网络,而不是通过它访问互联网,那么相同的批准步骤也适用于向 tailnet 的其他节点发布该子网。从一个节点发布单个应用,而不是通过该节点路由整个网络,则是另一项工作;serve 和 funnel 是实现该功能的两种方式。不过,两者都依赖 Tailscale 自带的证书和入口机制,因此应将它们视为托管 tailnet 功能,而不是 headscale 提供的功能。
DERP,以及直连失败时中继流量的组件
DERP(数据包指定加密中继)是备用路径。当两个节点无法建立直接的 WireGuard 连接时,通常是因为它们都位于严格的 NAT(网络地址转换)之后,此时会改为通过中继发送数据包。中继不持有任何密钥,因此无法读取您的流量。但它可以看到哪些节点正在通信,以及传输了多少数据。
请明确默认配置的行为。Headscale 默认指向 https://controlplane.tailscale.com/derpmap/default,并使用 auto_update_enabled: true 和 update_frequency: 3h,因此控制平面由您管理,而中继由 Tailscale 提供。对大多数用户来说,这是合理的取舍。如果您不能接受这种方式,请运行自己的中继。
要运行自己的中继,请在 config.yaml 的 derp.server 下设置 enabled: true,将随附的 ipv4: 198.51.100.1 和 ipv6: 2001:db8::1 替换为中继的公网地址,或者删除这两个设置以使用 DNS,然后重启 headscale,并通过 sudo ufw allow 3478/udp 放行 STUN(NAT 会话穿越工具)端口。这些地址设置会覆盖 DNS,因此保留任一占位地址都会使客户端连接到不可达的地址。配置文件明确说明了要求:server_url 必须使用 https,因为 DERP 要求 TLS。清空 derp.urls 列表会从映射中移除 Tailscale 的中继;执行此操作前,请确保仍有可用的内置中继或自定义中继。Headscale 不会在中继映射为空时启动;如果映射中只有一个不可达的中继,无法直连的节点将无法通信。
在客户端上,tailscale netcheck 会显示已知各中继区域的延迟;tailscale status 会显示活动对等连接,其中 direct 表示地址,relay 表示区域代码。卡在 relay 的对等节点需要检查直连路径,包括 NAT 和防火墙规则。处于 direct 但速度仍然很慢的对等节点则是另一类问题,通常应检查 MTU,而不是隧道本身。
为什么节点显示为离线?
代理正在丢弃升级请求。 其特征是 /health 返回 200,但新节点注册失败,或现有节点无法重新连接。控制连接是一个携带 Upgrade: tailscale-control-protocol 的 POST 请求。如果代理不转发该请求,注册以及报告节点状态的通道都会被阻断。将 nginx 配置与上文的升级配置行进行比较,或切换到 Caddy,以排除代理问题。
公网控制地址已更改。 节点会持续连接通过 --login-server 配置的地址;更改 server_url 不会更新客户端中的该设置。如果旧地址已无法访问服务器,请在每个节点上运行 sudo tailscale up --login-server https://headscale.example.com --force-reauth。
客户端未运行。 在节点上执行 sudo systemctl is-active tailscaled 和 sudo journalctl -u tailscaled -n 50 --no-pager。如果客户端无法解析或访问您的域名,会在这些日志中记录重试信息。
密钥已过期。 下一节将介绍此问题。
测试时,可在 VPS 上运行 sudo journalctl -u headscale -f 以监控服务器端,并在客户端上重启 tailscaled。同时检查客户端日志:请求到达 headscale 并不保证会在默认日志级别下生成日志行,因此服务器日志没有输出,不能单独证明存在 DNS、防火墙或代理故障。
密钥过期,以及数周后停止工作的节点
这里有两种独立的过期机制。混淆它们会浪费排障时间。
预授权密钥会按设计快速过期。默认有效期为 1 小时,且只能使用 1 次。如果 tailscale up 拒绝该密钥,请在服务器上生成新密钥,不要修改客户端上的任何内容。
节点密钥的有效期更长。config.yaml 的 node 部分用于设置 expiry: 0,而 0 表示没有默认过期时间;但显式设置的客户端过期时间或 OIDC 过期时间仍可能生效。带标签的节点不受自动过期影响,但管理员可以显式使其过期。如果希望非带标签注册默认有效 180 天,请设置 expiry: 180d;但客户端请求的过期时间优先级更高,OIDC 也可以覆盖该默认值。修改此设置不会追溯影响现有注册。设置了过期时间的节点必须在截止时间前重新进行身份验证,使用 sudo tailscale up --login-server https://headscale.example.com --force-reauth。无人重新进行身份验证的无头服务器,会在其注册过期后断开网络连接。
有人丢失笔记本电脑时,可以手动处理。sudo headscale nodes list 可提供节点 ID,随后使用 sudo headscale nodes expire -i 3 将该节点注销,再使用 sudo headscale nodes delete -i 3 将其从网络中完全移除。
备份与升级
/var/lib/headscale 和 /etc/headscale 共同构成完整的服务器数据。复制这两个文件前先停止服务,因为 SQLite 可能仍有写入操作,在负载期间复制数据库可能导致数据不一致。
sudo systemctl stop headscale
sudo tar czf /root/headscale-state.tgz -C /var/lib headscale
sudo tar czf /root/headscale-config.tgz -C /etc headscale
sudo systemctl start headscale
sudo chmod 600 /root/headscale-*.tgz将这两个文件复制到服务器之外。它们包含私钥和所有注册信息,因此应像保护服务器本身一样妥善保护。从 VPS 执行 restic 备份介绍了如何按计划执行加密备份。
升级流程与安装流程相同:下载新的 .deb、sudo apt install ./headscale.deb,然后重启服务,并重新执行 is-active 和 /health 检查。自 0.29 起,升级路径受到严格限制。不能跳过次要版本,也不能降级到更早的次要版本。每次只升级一个次要版本,在每一步之前创建备份,并先阅读该版本的发行说明,因为同一版本更改了 ACL 策略行为,并移动了多个配置项。版本 0.29.6 还包含证书验证和依赖项的安全更新,因此现有的 0.29 安装应升级到此补丁版本。
FAQ
安装 .deb 后,headscale 是否已经在运行?
是。软件包安装完成后会立即启动服务,使用随包提供的 /etc/headscale/config.yaml。该服务仅监听 loopback,并将 server_url 设置为 http://127.0.0.1:8080。全新安装不会启用该服务,因此在运行 sudo systemctl enable headscale 之前,它不会在系统启动时自动启动。随包提供的地址无法供客户端使用,因此请编辑 server_url、listen_addr 和 base_domain,然后运行 sudo systemctl restart headscale,并使用 sudo systemctl is-active headscale 确认。必须执行 restart:headscale 仅在启动时读取该文件,而对已在运行的服务执行 enable --now 只会启用开机启动,不会重启服务,因此旧值仍在生效。如果重启后服务失败,sudo journalctl -u headscale -n 50 --no-pager 会指出问题所在。在此阶段,问题几乎总是 YAML 错误,因为 headscale 会在绑定端口前解析整个文件。
我是否仍需在设备上安装普通的 Tailscale 客户端?
是。Headscale 只替代控制服务器。每个节点都运行 Tailscale 官方客户端,并使用 sudo tailscale up --login-server https://headscale.example.com 将其指向您的服务器。该标志是标准客户端的一部分,因此无需修补或重新构建客户端。
我的流量会经过 headscale 服务器吗?
通常不会。Headscale 负责协调网络并分发密钥和地址,数据路径则由节点之间的 WireGuard 直接建立。只有当两个节点无法直接访问、转而使用 DERP 中继时,流量才会绕行;使用随包提供的配置时,这些中继是 Tailscale 的公共中继。请在节点上运行 tailscale status,查看指定对等节点是 direct,还是位于 relay 上。
为什么我的节点注册后仍处于离线状态?
如果节点出现在 headscale nodes list 中但始终不上线,请检查其控制连接,包括反向代理。该连接是一个 HTTP upgrade 请求,以 POST 方式发送,并带有 Upgrade: tailscale-control-protocol 请求头。除非使用 proxy_set_header 显式转发 Upgrade 和 Connection 请求头,否则 nginx 会丢弃该请求;上面的配置通过其 map $http_upgrade $connection_upgrade 块完成了转发。丢弃这些请求头也会阻止新节点注册。Caddy 无需额外配置即可转发该请求,因此可以快速用于测试问题是否出在代理上。
headscale 是否需要域名和 TLS?
对于此配置,是的:请使用域名和 HTTPS。证书也可以覆盖裸 IP 地址,但配置文件规定 DERP 必须使用 TLS。域名配合 Caddy 大约需要五分钟即可完成配置,并提供能够自动续期的 HTTPS 端点。也可以使用纯 HTTP,但 headscale 文档强烈建议生产环境在端口 443 上使用 HTTPS,并指出 Tailscale 客户端在某些情况下默认使用端口 443 上的 HTTPS。