Headscale 运维手册
Headscale v0.29.5 日常运维闭环:巡检清单、节点与用户管理(rename/expire/delete)、preauthkey 生命周期、SQLite 在线备份与恢复演练、Prometheus 指标、升级回滚实测与故障排查手册,命令回显逐条实测采集。
Headscale 运维手册
> 目标:给已上线的 Headscale v0.29.5 服务端一份日常运维闭环手册:每日巡检清单、节点与用户管理、预授权密钥管理、SQLite 备份与恢复演练、监控指标暴露、版本升级与回滚、故障排查手册。全部命令与回显在 Ubuntu 22.04.5 + Headscale v0.29.5 环境实测采集(node1/node2 两台客户端在线)。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Headscale:v0.29.5(二进制 /usr/local/bin/headscale + systemd 服务 headscale.service)
- 配置:/etc/headscale/config.yaml;数据:/var/lib/headscale(db.sqlite + noise_private.key)
- 控制面地址:http://192.168.32.2:8080(HTTP)、127.0.0.1:9090(metrics,实测已改 0.0.0.0:9090)
- 节点:node1(192.168.32.3,tailnet IP 100.64.0.1)、node2(192.168.32.4,tailnet IP 100.64.0.2),Tailscale 客户端 1.104.1
- 注册用户:demo(users 表 ID 1)
二、日常巡检
巡检目标是「服务活着、端口在听、节点在线、日志无错」。以下命令均实测,可整理成每日脚本分步执行。
2.1 服务状态检查
systemctl is-enabled headscale
systemctl is-active headscale
systemctl status headscale --no-pager -l
实测输出:
enabled
active
● headscale.service - headscale controller
Loaded: loaded (/etc/systemd/system/headscale.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2026-10-17 22:07:04 UTC; 1min 22s ago
Main PID: 3928 (headscale)
Tasks: 14 (limit: 5753)
Memory: 20.9M
CPU: 4.204s
Memory 常驻约 20MB,可作为基线;若明显增长需排查(见第九章故障排查)。
2.2 服务健康与端口检查
headscale health
curl -s http://127.0.0.1:8080/health
ss -tlnp | grep -E "8080|9090|50443"
实测输出(headscale health 成功时静默无输出,退出码 0;/health 返回固定 JSON):
{"status":"pass"}
LISTEN 0 4096 *:9090 *:* users:(("headscale",pid=3928,fd=14))
LISTEN 0 4096 *:8080 *:* users:(("headscale",pid=3928,fd=13))
💡 说明:监听列表里没有 50443 属正常——未配置 TLS 证书时 gRPC 远程管理不启用,本机 CLI 走 /var/run/headscale/headscale.sock。进程归属确认可用
ps -eo user,pid,cmd | grep [h]eadscale,实测为headsca+ 3928 /usr/local/bin/headscale serve(以 headscale 用户运行)。
2.3 日志巡检
journalctl -u headscale --no-pager --since today | grep -cE "ERR|Error"
journalctl -u headscale --no-pager -n 5
实测输出(正常时段错误计数为 0,最近 5 行为启动后的常规信息):
0
Oct 17 21:48:25 hs-controller headscale[2745]: 2026-10-17T21:48:25Z INF listening and serving HTTP on: 0.0.0.0:8080
Oct 17 21:48:25 hs-controller headscale[2745]: 2026-10-17T21:48:25Z INF listening and serving debug and metrics on: 127.0.0.1:9090
关注三类日志:INF 级的 listening/starting(启动正常)、ERR/Fatal config error(配置问题,见第九章)、重启计数(systemd 的 restart counter 增长说明进程反复退出)。
2.4 网络状态巡检
headscale nodes list
实测输出:
ID | Hostname | Name | MachineKey | NodeKey | User | Tags | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
1 | node1 | node1 | [3yxWo] | [103CQ] | demo | | 100.64.0.1 | false | 2026-10-17 22:07:14 | N/A | online | no
| | | | | | | fd7a:115c:a1e0::1 | | | | |
2 | node2 | node2 | [I2g49] | [OoFbg] | demo | | 100.64.0.2 | false | 2026-10-17 22:05:51 | N/A | online | no
| | | | | | | fd7a:115c:a1e0::2 | | | | |
巡检关注点:Connected 列是否 online、Expired 是否 yes(过期节点需重注册或手工处理)、Last seen 是否长时间未更新(可能节点离线)。
三、节点与用户管理
3.1 节点列表与重命名
headscale nodes list
重命名节点(把 ID 3 的节点改名为 web-01;ID 位置参数不被接受,必须用 --identifier):
headscale nodes rename web-01 --identifier 3
headscale nodes list | grep web-01 -A 1
实测输出:
Node renamed
3 | node3 | web-01 | [B3drU] | [6oAVY] | demo | | 100.64.4.1 | false | 2026-10-17 21:59:43 | N/A | online | no
| | | | | | | fd7a:115c:a1e0::3 | | | | |
注意:rename 只改控制面 Name(MagicDNS 域名随之变为 web-01.<base_domain>),节点操作系统主机名不变,客户端无感。
3.2 节点过期与重新认证
让指定节点立即过期(密钥失效、强制重新认证):
headscale nodes expire --identifier 3
headscale nodes list | grep web-01 -A 1
实测输出(Expiration 从 N/A 变为当前时间,Connected 转 offline):
Node expired
3 | node3 | web-01 | [B3drU] | [6oAVY] | demo | | 100.64.4.1 | false | 2026-10-17 21:59:43 | 2026-10-17 22:00:16 | offline | yes
| | | | | | | fd7a:115c:a1e0::3 | | | | |
节点侧状态(node3 上执行 tailscale status):
Logged out.
过期节点重新认证需在客户端重新执行 tailscale up --login-server http://192.168.32.2:8080 走注册流程(或用预授权密钥)。
3.3 节点删除
headscale nodes delete --identifier 3 --force
headscale nodes list
实测输出:
Node deleted
ID | Hostname | Name | MachineKey | NodeKey | User | Tags | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
1 | node1 | node1 | [3yxWo] | [103CQ] | demo | | 100.64.0.1 | false | 2026-10-17 21:59:07 | N/A | online | no
2 | node2 | node2 | [I2g49] | [OoFbg] | demo | | 100.64.0.2 | false | 2026-10-17 21:59:07 | N/A | online | no
--force 跳过交互确认;删除后节点需重新注册才能回到网络,客户端本地残留状态可用 tailscale logout 清理。
3.4 用户管理
headscale users create ops
headscale users list
实测输出(demo 为既有用户,ops 为新创建):
User created
ID | Name | Username | Email | Created
1 | | demo | | 2026-10-17 21:48:51
2 | | ops | | 2026-10-17 22:34:18
用户是注册与策略的归属单位(preauthkey --user、auth register --user 均引用它),删除用户前需先迁移或删除其名下节点。
四、preauthkey 管理
预授权密钥是批量接入的主要凭据,生命周期管理三点:创建、查看、撤销。
headscale preauthkeys create --user 1 --reusable --expiration 24h
headscale preauthkeys list
实测输出:
hskey-auth-YeO_f1ecaQRq-P649QSkPdzna-EzcXUfXmjwZvcMER75HgzhcEGwxEuwn7yNs98qJFyBhwGGtnrwk
ID | Key/Prefix | Reusable | Ephemeral | Used | Expiration | Created | Owner
1 | hskey-auth-YeO_f1ecaQRq-*** | true | false | false | 2026-10-18 21:51:12 | 2026-10-17 21:51:12 | demo
参数说明:--user 1 归属用户 ID;--reusable 允许多节点复用(不加则一次性,用一次即失效);--expiration 支持 30m/24h 等可读时长(默认 1h);--ephemeral 创建临时节点密钥(节点断开后自动清除);--tags 可在注册时自动给节点打标签。list 中密钥脱敏为前缀 + ***,完整值只在 create 时返回一次,创建后需妥善保存。
撤销(立即作废):
headscale preauthkeys expire --id 1
headscale preauthkeys list
实测输出(Expiration 被改写为当前时刻,密钥即刻失效):
Key expired
ID | Key/Prefix | Reusable | Ephemeral | Used | Expiration | Created | Owner
1 | hskey-auth-YeO_f1ecaQRq-*** | true | false | false | 2026-10-17 22:08:11 | 2026-10-17 21:51:12 | demo
注意 expire 用 --id 指定密钥 ID(list 第一列),不能用 --user 批量;密钥泄露场景先 expire 再重建,同时排查泄露路径。
五、备份与恢复
5.1 备份内容与在线备份
必须备份的三样:SQLite 数据库、Noise 私钥、配置文件。数据库在 WAL 模式下有伴随文件(-shm/-wal),直接 cp 存在一致性风险,标准做法是 sqlite3 在线备份:
mkdir -p /var/backups/headscale
ls -l /var/lib/headscale/
sqlite3 /var/lib/headscale/db.sqlite ".backup /var/backups/headscale/db-$(date +%F).sqlite"
ls -l /var/backups/headscale/
实测输出(备份目录需先创建,否则 .backup 报 Error: cannot open;备份文件属主为执行用户):
total 124
-rw-r--r-- 1 headscale headscale 73728 Oct 17 22:22 db.sqlite
-rw-r--r-- 1 headscale headscale 32768 Oct 17 22:23 db.sqlite-shm
-rw-r--r-- 1 headscale headscale 12392 Oct 17 22:23 db.sqlite-wal
-rw------- 1 headscale headscale 72 Oct 17 22:23 noise_private.key
-rw-r--r-- 1 root root 73728 Oct 17 22:23 db-2026-10-17.sqlite
密钥与配置一并备份:
mkdir -p /var/backups/headscale/
cp -a /var/lib/headscale/noise_private.key /var/backups/headscale/
cp -a /etc/headscale/config.yaml /var/backups/headscale/config-$(date +%F).yaml
备份频率建议:日常每日一备(数据库小,秒级完成);批量注册节点前后各加一备。校验备份可用性:
sqlite3 /var/backups/headscale/db-2026-10-17.sqlite ".tables"
sqlite3 /var/backups/headscale/db-2026-10-17.sqlite "SELECT count(*) FROM nodes;"
实测输出(7 张业务表、2 个节点,与线上一致):
api_keys migrations policies users
database_versions nodes pre_auth_keys
2
5.2 恢复演练
恢复流程:停服务 → 替换数据库文件 → 修属主 → 启动 → 验证。完整实测:
systemctl stop headscale
rm /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite-shm /var/lib/headscale/db.sqlite-wal
cp /var/backups/headscale/db-2026-10-17.sqlite /var/lib/headscale/db.sqlite
chown headscale:headscale /var/lib/headscale/db.sqlite
systemctl start headscale
systemctl is-active headscale
headscale nodes list
实测输出(服务恢复 active,节点数据完整):
active
ID | Hostname | Name | MachineKey | NodeKey | User | Tags | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
1 | node1 | node1 | [3yxWo] | [103CQ] | demo | | 100.64.0.1 | false | 2026-10-17 22:04:40 | N/A | offline | no
2 | node2 | node2 | [I2g49] | [OoFbg] | demo | | 100.64.0.2 | false | 2026-10-17 22:05:51 | N/A | offline | no
恢复后节点重连实测分两种情况:
- 只回滚数据库、noise_private.key 用的是后来新生成的(即上例状态):节点不会立即自动恢复 online,在节点侧执行
systemctl restart tailscaled触发重连后恢复(实测两节点重启 tailscaled 后服务端headscale nodes list | grep -c online返回 2); - 数据库与 noise_private.key 一并回滚(灾难恢复标准做法):把备份的 key 复制回 /var/lib/headscale 并修属主后重启服务,节点在 30 秒内自动重连(实测
grep -c online无人工干预返回 2),节点间 ping 与 MagicDNS 解析均正常(0% packet loss)。恢复演练建议每季度做一次,避免「有备份、不会恢复」。
六、监控告警
6.1 指标端点
Headscale 原生暴露 Prometheus 指标(metrics_listen_addr 控制,默认 127.0.0.1:9090):
curl -s http://127.0.0.1:9090/metrics | head -3
curl -s http://127.0.0.1:9090/metrics | grep -cE "^# HELP"
实测输出(56 个指标族,含 Go 运行时与 headscale 业务指标):
# HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 6.8741e-05
指标含 Go 运行时(gc/内存/goroutine)与 headscale 业务指标(map 更新、注册计数等)。跨机采集需把 metrics_listen_addr 改为 0.0.0.0:9090(实测改动后从 node1 抓取成功),并确认防火墙仅对监控网段放行 9090。
6.2 健康检查与告警建议
- 存活探针:GET http://<服务端>:8080/health,期望 {"status":"pass"}(实测固定返回该 JSON),非 200 即告警;
- 进程探针:systemctl is-active headscale,非 active 告警;
- 数据探针:headscale nodes list 输出中 online 数量低于阈值告警(可用 -o json 输出解析);
- 日志告警:journalctl 出现 Fatal config error 或 restart counter 连续增长告警。
七、容量与清理
- 服务端资源占用极低:实测常驻内存约 20MB、SQLite 全库 72KB(2 节点),单实例支撑数百节点无压力;容量瓶颈通常在节点数与策略复杂度(ACL 规则越多 map 编译越慢);
- 节点清理:测试节点及时
headscale nodes delete --identifier <ID> --force,过期密钥用headscale preauthkeys expire --id <ID>撤销; - 临时节点:批量 CI/短时任务建议用
--ephemeral密钥接入,节点断开后由控制面按 node.ephemeral.inactivity_timeout(默认 30m)自动清理,避免 nodes 表膨胀; - 数据库体积:可用
ls -lh /var/lib/headscale/db.sqlite观察;SQLite 自带空间回收,膨胀明显时可停服后sqlite3 db.sqlite "VACUUM;"; - 服务端不承担流量转发(数据面点对点),无带宽容量规划负担,仅需保证 8080/3478 的连接数。
八、升级与回滚
升级采用「覆盖二进制 + 重启」方式,数据库 schema 由新版本自动迁移。实测演示 0.29.5 → 0.29.4 → 0.29.5 的完整降级/升级闭环。
8.1 升级到新版本
cd /tmp
curl -fsSL -O https://github.com/juanfont/headscale/releases/download/v0.29.5/headscale_0.29.5_linux_amd64
install -m 0755 headscale_0.29.5_linux_amd64 /usr/local/bin/headscale
systemctl restart headscale
systemctl is-active headscale
headscale version
(升级到更新版本时,把 URL 与文件名中的 v0.29.5 替换为目标 Release 版本号即可。)
8.2 回滚实测(0.29.5 → 0.29.4)
覆盖安装旧版二进制并重启:
cd /tmp && curl -fsSL -O https://github.com/juanfont/headscale/releases/download/v0.29.4/headscale_0.29.4_linux_amd64
install -m 0755 headscale_0.29.4_linux_amd64 /usr/local/bin/headscale
headscale version
systemctl restart headscale
systemctl is-active headscale
headscale nodes list
实测输出:
headscale version v0.29.4
commit: 8106636c7f8d0cf8c9d4fb0beae94415069b768b
build time: 2026-09-23T19:19:22Z
built with: go1.26.5 linux/amd64
active
ID | Hostname | Name | MachineKey | NodeKey | User | Tags | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
1 | node1 | node1 | [3yxWo] | [103CQ] | demo | | 100.64.0.1 | false | 2026-10-17 22:04:40 | N/A | offline | no
8.3 升回 0.29.5 并验证数据完好
install -m 0755 /tmp/headscale_0.29.5_linux_amd64 /usr/local/bin/headscale
systemctl restart headscale
systemctl is-active headscale
headscale version
headscale nodes list
实测输出:
active
headscale version v0.29.5
commit: 0f7d1e536bed9f01dd7161f7637af19bb0908335
build time: 2026-10-13T12:35:52Z
built with: go1.26.8 linux/amd64
ID | Hostname | Name | MachineKey | NodeKey | User | Tags | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
1 | node1 | node1 | [3yxWo] | [103CQ] | demo | | 100.64.0.1 | false | 2026-10-17 22:07:14 | N/A | online | no
2 | node2 | node2 | [I2g49] | [OoFbg] | demo | | 100.64.0.2 | false | 2026-10-17 22:07:14 | N/A | online | no
升级要点总结:版本切换只影响二进制,配置与数据库保留原位;重启后节点自动重连(offline → online,实测 20 秒内);跨多个 minor 版本升级前先做数据库备份(第五章),并阅读 Release Notes 中标注的破坏性变更。
九、故障排查手册
现象一:服务反复重启,systemd 显示 activating。
定位:journalctl -u headscale -n 20 | grep -E "Error|error"。实测两类根因:配置校验失败(如 Fatal config error: dns.nameservers.global must be set when dns.override_local_dns is true);策略文件解析失败(parsing policy: host not defined in policy: "demo",v0.29 对裸用户名引用不兼容)。处理:修正 /etc/headscale/config.yaml 或 ACL 文件后 systemctl restart headscale,先 headscale configtest 预检可避免此类事故。
现象二:客户端无法连接控制面。
定位链路:服务端 systemctl is-active headscale → 客户端 curl -s http://192.168.32.2:8080/health 是否返回 {"status":"pass"} → 中间防火墙 8080/tcp。三者都通仍失败时,核对客户端 tailscale up --login-server 的地址与配置中 server_url 是否完全一致(协议、IP/域名、端口任一不同都会导致注册异常)。
现象三:节点长期 offline。
服务端看 headscale nodes list 的 Expired 列:为 yes 则密钥已过期,客户端需重新 tailscale up 认证(或改用新预授权密钥);为 no 则在节点端 systemctl restart tailscaled 后观察(实测服务端重启场景下节点 20 秒内自动恢复 online)。
现象四:改了 ACL 但行为没变。
ACL 在服务端重启时重新编译下发,确认 systemctl restart headscale 已执行;节点侧用 tailscale debug netmap | grep -A 20 PacketFilter 核对下发的过滤规则是否为最新(实测端口收紧后 PacketFilter 只剩对应端口段)。
现象五:内存/指标异常增长。
systemctl status headscale 看 Memory(基线约 20MB),curl -s http://127.0.0.1:9090/metrics | grep goroutine 看 goroutine 数;异常时先查 journalctl 是否有 ERR 刷屏,必要时重启服务并保留日志反馈社区 issue。
现象六:数据库损坏(服务启动报 SQLite 错误)。 停服务后用最近备份按第五章 5.2 节恢复演练流程替换 db.sqlite 并修属主;无备份时的最后手段是保留损坏文件联系社区(noise_private.key 未丢时节点身份可保留)。
十、常见问题(FAQ)
Q1:巡检的最小命令集是什么?
四条:systemctl is-active headscale(active)、curl -s http://127.0.0.1:8080/health({"status":"pass"})、headscale nodes list(在线数)、journalctl -u headscale --no-pager --since today | grep -cE "ERR|Error"(0)。
Q2:备份必须停服吗? 不需要。sqlite3 的 .backup 命令在线执行一致性快照(实测服务运行中完成);只有把备份文件直接 cp 回数据目录的恢复动作需要停服。
Q3:升级会断开现有连接吗? 已建立的 WireGuard 隧道不经过控制面,升级重启期间存量隧道不中断;影响的是重启窗口内的新注册与拓扑下发(节点重连后自动同步,实测 offline 状态数十秒内恢复)。
Q4:preauthkeys 的 Used 列一直显示 false? v0.29.5 中该列对可复用密钥不实时计数,判断密钥是否可用以 Expiration 是否过期、是否已 expire 为准,勿以 Used 作为使用量依据。
Q5:如何让节点定期强制重新认证?
两条路径:全局默认在 config.yaml 设 node.expiry(如 180d);单节点立即过期用 headscale nodes expire --identifier <ID>(实测客户端随即 Logged out,重新 up 完成认证)。