Headscale 容器化部署完整指南:一条 docker run 跑起私有 Tailscale 控制服务器
Headscale 是一个开源、可自托管的 Tailscale 控制服务器实现。装好它之后,所有 Tailscale 客户端都指向你自己部署的这台服务,设备注册、命名网络、DERP 中继和 ACL 策略全部由你掌控,不再依赖官方控制平面。
整篇文章按四步展开:先看它到底能替你做什么;再按当前版本把容器从零跑通,包括建目录、改配置、启动、建用户、发密钥、注册节点;然后是日常会用到的核心命令速查表;最后收录几个高频翻车场景的修复方法,外加一份紧凑的升级操作清单。
它到底能干什么

- 替代官方 controlplane:客户端 tailscale up –login-server 指向你的域名,注册、登录、节点管理全在自己手里。
- 用 users 做身份隔离,用 policy(ACL)控制哪些节点之间能互通、谁能走出口路由。
- 用 preauthkeys 给无浏览器环境的机器发预授权密钥,一条命令完成准入。
- 自带 DERP 中继配置与健康检查端点(/health),跨 NAT 不通时有兜底。
和直接用 Tailscale 官方服务相比,差异点就在于控制面可审计、策略可版本化、数据不出自己的机器;代价是要自己维护这台服务器和它的前置 TLS 反代。
当前版本完整部署实操
容器跑起来之前,先准备好两个挂载目录:一个放配置,一个放 SQLite 数据库。目录结构一旦定下,升级时直接复用即可。
# 目录准备:config 放配置,lib 放数据库
mkdir -p ./headscale/{config,lib}
cd ./headscale
# 从仓库拉一份配置模板作为起点(版本号按实际发布替换)
curl -o config/config.yaml https://gitcode.com/GitHub_Trending/he/headscale/raw/main/derp-example.yaml
配置文件里有几个参数必须改对,改错会导致容器启动即退出:
| server_url | 客户端访问控制面的地址,必须带 https 且有有效证书 | 不能与 base_domain 同域且无法区分 |
| dns.base_domain | MagicDNS 的私有域名 | 用你自己的域名,如 ts.example.net |
| derp.auto_update / DERP 区域 | 中继配置 | 官方 DERP 区域建议自行部署替代,模板里 1: null 即禁用内置区域 |
注意:server_url 的域名必须能被客户端真实解析到(前置 nginx/caddy 反代到 8080 端口),否则客户端注册时会卡在连接超时。配置项全集见 docs/ref/configuration.md。
目录和配置就位后,启动容器。下面这条命令一次把只读文件系统、tmpfs、卷挂载、端口和健康检查都配齐:
docker run \\
–name headscale \\
–detach \\
–read-only \\
–tmpfs /tmp \\
–tmpfs /var/run/headscale \\
–volume "$(pwd)/config:/etc/headscale:ro" \\
–volume "$(pwd)/lib:/var/lib/headscale" \\
–publish 127.0.0.1:8080:8080 \\
–publish 127.0.0.1:9090:9090 \\
–health-cmd "CMD headscale health" \\
headscale/headscale:latest \\
serve
serve 是容器的启动子命令,镜像入口默认就是它,命令末尾不需要再带 headscale 前缀。端口先绑 127.0.0.1,等反代跑通后再改成 0.0.0.0 对外暴露。
容器跑起来之后,第一件事是确认它活着:
docker logs –follow headscale
curl http://127.0.0.1:8080/health
健康端点返回正常后,往网络里加第一个成员:建用户,再给这个用户发一把可复用的预授权密钥(默认 1 小时过期,–expiration 可调):
# 建用户
docker exec -it headscale headscale users create alice
# 发密钥:可复用、24 小时有效
docker exec -it headscale headscale preauthkeys create –user alice –reusable –expiration 24h
有了密钥,客户端侧就能自助注册。Linux 机器上执行 tailscale up 并指向你的服务地址,把密钥作为 –auth-key 传入:
tailscale up –login-server https://headscale.example.com \\
–auth-key <上一步输出的密钥>
如果走的是 Web 认证流程(浏览器登录),客户端会返回一个 Auth ID,在服务器上批准即可,这条命令是当前的标准写法:
docker exec -it headscale headscale auth register –user alice –auth-id <AUTH_ID>
注:旧版 headscale nodes register –user <user> –key <KEY> 写法已在源码中标记废弃,等价能力由 headscale auth register 承担,老脚本需要替换。命令定义可见 cmd/headscale/cli/nodes.go 和 cmd/headscale/cli/auth.go。
注册完成后回服务器侧验收:
docker exec -it headscale headscale nodes list -u alice
节点出现且在线,整条链路就通了。需要出口路由时再批准路由:
# 查看某节点宣告的路由
docker exec -it headscale headscale nodes list-routes –identifier 1
# 批准 0.0.0.0/0 出口路由
docker exec -it headscale headscale nodes approve-routes –identifier 1 –routes 0.0.0.0/0,::/0
核心命令速查
按使用频率排的常用命令都在容器里执行(原生安装则去掉 docker exec -it headscale 前缀):
| headscale users create | 创建用户 | <USER> 用户名 |
| headscale preauthkeys create | 发预授权密钥 | –user –reusable –expiration |
| headscale nodes list | 查看节点与状态 | -u 按用户过滤 |
| headscale auth register | 批准节点注册 | –user –auth-id |
| headscale nodes approve-routes | 批准出口路由 | –identifier –routes |
| headscale policy get / set | 查看/下发 ACL 策略 | set 需 -f 指定 HuJSON 文件 |
踩坑点:
策略文件是 HuJSON 格式(JSON 里可以写注释),policy set 之前建议先 policy check 校验一遍,语法错误会被整体拒绝。
nodes list-routes、approve-routes 的定位参数统一叫 –identifier(也可写 -i),旧版路由命令的 -n 节点参数不再沿用。
预授权密钥默认 1 小时过期且一次性;批量装机时记得加 –reusable 并拉长 –expiration,否则第二台机器注册时密钥已经失效。命令实现见 cmd/headscale/cli/preauthkeys.go。
翻车现场与修复
容器起来就退,日志报找不到 config.yaml
现象:docker run 后容器立刻退出,日志里出现 config.yaml: no such file or directory。
根因:挂载点存在但 config.yaml 没放进去,或模板下错文件(比如把 derp 示例当主配置,缺了 server_url 等必填项)。
修复:
ls -la ./headscale/config/ # 确认 config.yaml 存在且内容完整
docker logs headscale # 重跑前先看完整报错
docker logs –follow headscale # 修完配置后重新跟踪日志
users create 报 user already exists,但列表里没有这个人
现象:headscale users create alice 提示重名,users list 却查不到。
根因:早前版本调整过用户 ID 管理逻辑,数据库里可能残留一条无关联记录的 user 行;也可能是同名但大小写不同。
修复(镜像默认基于 distroless,没有 shell,换 -debug 标签的镜像才有 Busybox 可以进):
# debug 镜像与正式版数据兼容,可交替使用
docker run -it –rm -v ./headscale/lib:/var/lib/headscale \\
headscale/headscale:latest-debug sh
# 进入后定位残留记录并清理
sqlite3 /var/lib/headscale/db.sqlite "SELECT id, name FROM users;"
sqlite3 /var/lib/headscale/db.sqlite "DELETE FROM users WHERE name='alice';"
表结构可对照 hscontrol/db/schema.sql 确认字段。
节点显示在线,但互相 ping 不通
现象:nodes list 里两台设备都 online,跨节点流量却不通。
根因:ACL 策略没放行——默认策略下节点之间并不互通;或者两端 NAT 都不利而 DERP 中继被禁用。
修复:
# 看当前策略
docker exec -it headscale headscale policy get
# 下发一条允许 tag 互访的 HuJSON 策略
docker exec -it headscale headscale policy set -f ./policy.hujson
策略写法与内置动作见 docs/ref/policy.md。
nodes register 报 deprecated
现象:沿用老脚本执行 headscale nodes register,命令能跑但输出带废弃警告。
根因:该命令在当前版本已标记 Deprecated,注册入口统一到 auth 子命令下。
修复:
docker exec -it headscale headscale auth register –user <USER> –auth-id <AUTH_ID>
升级操作清单
注意:较新的发布只支持从紧邻的上一大版本升级(例如 0.30 只接受 0.29 的库),跨多个版本要逐级升;且凭证表结构在 0.30 有不可回退的合并,备份是唯一回退手段,详见 CHANGELOG.md。
headscale 的容器化部署本质就是"一份配置 + 一个数据库目录 + 一条 serve 命令",命令面不大,跑通一次后日常就是上面速查表里那几条。
延伸阅读:docs/about/faq.md
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心






评论前必须登录!
注册