🦎 Headscale 自建组网中枢:把 Tailscale 控制权攥在自己手里

不想让组网数据流经官方云?Headscale 让你用一台 VPS 就把 Tailscale 的控制面完全私有化——IP、DNS、ACL、DERP 中继全部自主可控。
Headscale 完整部署
1. 概述
- Headscale 是 Tailscale 官方云控制面的开源替代品,客户端仍使用官方 Tailscale。
- 适合场景:设备数量多/私有化要求高/不希望数据过官方服务器,元素全部自主可控(IP 段、DNS、ACL、DERP 中继、审计)。
- 架构:一台公网 VPS 跑控制面(Headscale)+ 各设备跑客户端(Tailscale)接入。
- 版本注意:Headscale 迭代快,配置项随版本变化较大,请注意所用版本(如 v0.23.x / v0.27.x / 更新版)。本文以当前主流配置为准。
flowchart LR
subgraph 公网VPS
HS[Headscale 控制面<br/>:8080]
DERP[嵌入式 DERP 中继<br/>:3478/udp]
CADDY[反代 + HTTPS]
end
N1[设备 A · Tailscale] <-->|注册/心跳| HS
N2[设备 B · Tailscale] <-->|注册/心跳| HS
N1 <-->|NAT 打洞直连| N2
N1 <-->|打洞失败走 DERP| DERP
前置条件
- 一台公网 VPS(Debian 12 / Ubuntu 22.04+,1C/512M 以上即可)。
- 一个域名,解析一个子域名(如
hs.example.com)A 记录到 VPS,生产环境建议配 HTTPS。 - Docker + Docker Compose(或直接用二进制部署)。
2. 部署方式一:Docker Compose(推荐)
2.1 目录结构
sudo mkdir -p /opt/headscale/{config,data,run}
cd /opt/headscale
2.2 配置文件 /opt/headscale/config/config.yaml
容器内的路径是
/etc/headscale与/var/lib/headscale,与宿主机卷映射对应,别搞混。
server_url: https://hs.example.com # 客户端访问的地址(必须可被公网访问)
listen_addr: 0.0.0.0:8080 # 控制面监听(HTTP)
metrics_listen_addr: 127.0.0.1:9090 # Prometheus 指标
grpc_listen_addr: 127.0.0.1:50443 # 远程 CLI(建议仅本机)
noise:
private_key_path: /var/lib/headscale/noise_private.key # 启动自动生成
prefixes:
v4: 100.64.0.0/10 # Tailscale 标准 CGNAT 网段
v6: fd7a:115c:a1e0::/48 # IPv6 ULA
allocation: sequential # sequential | random
derp:
server:
enabled: true # 开启嵌入式 DERP(含 STUN)
region_id: 999 # 自定义区域 ID(避免与官方冲突)
region_code: "headscale"
region_name: "Headscale Embedded DERP"
verify_clients: true # 仅允许本组网设备使用
stun_listen_addr: "0.0.0.0:3478"
private_key_path: /var/lib/headscale/derp_server_private.key
automatically_add_embedded_derp_region: true
ipv4: 你的VPS公网IP # 必填!
ipv6: ""
urls:
- https://controlplane.tailscale.com/derpmap/default # 官方 DERP 兜底
paths: [] # 自定义 derp.yaml 路径
auto_update_enabled: true
update_frequency: 3h
database:
type: sqlite
debug: false
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true
log:
level: info
format: text
policy:
mode: file
path: /etc/headscale/acl.hujson # ACL 策略文件(见 ACL 笔记)
dns:
magic_dns: true
base_domain: example.com # MagicDNS 内网后缀
override_local_dns: true
nameservers:
global: ["1.1.1.1", "8.8.8.8"]
split: {}
search_domains: []
extra_records: []
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"
disable_check_updates: true
ephemeral_node_inactivity_timeout: 30m
randomize_client_port: false
taildrop:
enabled: true
精简版(不需要嵌入式 DERP 时)直接把 derp.server 关闭,urls 保留官方即可。
2.3 docker-compose.yml
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
command: serve
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
- ./run:/var/run/headscale
ports:
- "8080:8080" # 控制面 HTTP(一般反代后可不暴露公网)
- "3478:3478/udp" # 嵌入式 DERP 的 STUN(UDP,需公网)
networks:
- headscale-net
networks:
headscale-net:
name: headscale-net
启动:
sudo docker compose up -d
sudo docker compose logs -f headscale
3. 接入 HTTPS 反向代理
控制面建议走域名 + HTTPS(客户端登录时对证书敏感)。可选 Caddy / Nginx / Nginx Proxy Manager。
3.1 方案一:Caddy(全自动证书,最简单)
创建 Caddyfile,与 headscale 处于同一 compose 网络时直接用服务名:
hs.example.com {
reverse_proxy headscale:8080
}
容器:
caddy:
image: caddy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- headscale-net
3.2 方案二:Nginx(关键点)
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl http2;
server_name hs.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem; # 自备证书
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080; # 指向 headscale HTTP
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600;
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-Proto $scheme;
proxy_redirect http:// https://;
}
}
server { listen 80; server_name hs.example.com; return 301 https://$host$request_uri; }
4. 方式二:二进制 + systemd(对部署完全掌控)
# 下载二进制(到 GitHub Releases 取最新版)
curl -L -o /usr/local/bin/headscale https://github.com/juanfont/headscale/releases/download/<版本>/headscale_<版本>_linux_amd64
chmod +x /usr/local/bin/headscale
# 创建运行用户与目录
sudo useradd --home /var/lib/headscale --system headscale
sudo mkdir -p /etc/headscale /var/lib/headscale /var/run/headscale
sudo chown -R headscale:headscale /var/lib/headscale /var/run/headscale
# 写入 config.yaml(同 2.2,路径为 /etc/headscale/config.yaml)
sudo chown root:headscale /etc/headscale/config.yaml
sudo chmod 0640 /etc/headscale/config.yaml
创建 /etc/systemd/system/headscale.service:
[Unit]
Description=Headscale Control Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=headscale
Group=headscale
WorkingDirectory=/var/lib/headscale
ExecStart=/usr/local/bin/headscale serve
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
RuntimeDirectory=headscale
RuntimeDirectoryMode=0750
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/headscale /var/run/headscale
[Install]
WantedBy=multi-user.target
sudo -u headscale headscale configtest # 先校验配置
sudo systemctl daemon-reload
sudo systemctl enable --now headscale
sudo systemctl status headscale
5. 初始化
5.1 创建用户(namespace)
docker exec -it headscale headscale users create default
docker exec -it headscale headscale users create admin
docker exec -it headscale headscale users list
5.2 生成 API Key(供 UI / 脚本)
docker exec headscale headscale apikeys create --expiration 999d
5.3 生成预认证 Key(preauthkey,供无人值守设备)
docker exec headscale headscale preauthkeys create --user default --reusable --expiration 90d
6. 客户端接入
6.1 Linux
curl -fsSL https://tailscale.com/install.sh | sh
# 交互式注册(浏览器打开链接)
sudo tailscale up --login-server https://hs.example.com
# 或配合预认证 Key 免交互
sudo tailscale up --login-server https://hs.example.com \
--auth-key <preauthkey> \
--accept-routes \
--accept-dns=false
6.2 查看与注册(如果用了 –login-server 但需手动注册节点)
# 客户端命令输出注册 key(形如 nodekey:...),再在服务端执行:
docker exec headscale headscale nodes register --user default --key nodekey:xxxx
docker exec headscale headscale nodes list
6.3 Windows
- 正常安装 Tailscale 客户端后,导入注册表切换控制面,或用命令:
- 官方提供的
/windows注册表导入方式;手动改HKLM\SOFTWARE\Tailscale IPN下的LoginURL为自建地址(按官方指引为准)。
- 官方提供的
6.4 iOS / Android
- 安装官方 App 后,在登录页点击右上角菜单/图标(连击),进入"Use a custom server / Alternative server",填写
https://hs.example.com。
6.5 用 Docker 跑 Tailscale 节点(如 NAS 共享内网)
services:
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale
environment:
TS_AUTHKEY: <preauthkey>
TS_EXTRA_ARGS: --login-server=https://hs.example.com --advertise-routes=192.168.1.0/24 --accept-routes
TS_USERSPACE: "false"
volumes:
- ./state:/var/lib/tailscale
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
- NET_RAW
network_mode: host
restart: always
7. Web 管理面板(可选)
Headscale 本身是 CLI,可加可视化面板。
7.1 Headplane(新一代 UI,功能最全)
headplane:
image: ghcr.io/tale/headplane:latest
restart: unless-stopped
volumes:
- ./config/headplane.yaml:/etc/headplane/config.yaml
- ./data/headplane:/var/lib/headplane
- ./config:/etc/headscale:ro # 复用 headscale 配置
environment:
- HEADSCALE_ADDR=http://headscale:8080
networks: [headscale-net]
headplane.yaml 要点:
server:
host: "0.0.0.0"
port: 3000
cookie_secret: "<随机32位字符串: tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32>"
cookie_secure: true
data_path: "/var/lib/headplane"
headscale:
url: "http://headscale:8080"
public_url: "https://hs.example.com"
config_path: "/etc/headscale/config.yaml"
config_strict: true
integration:
docker:
enabled: false
- 访问:
https://hs.example.com/admin/(Caddy/Traefik 里再把 /admin 路由到 headplane:3000)。 - 首次打开需填写 5.2 生成的 API Key。
7.2 轻量替代:headscale-ui / headscale-admin
headscale-ui:
image: ghcr.io/gurucomputing/headscale-ui:latest
restart: unless-stopped
networks: [headscale-net]
8. 自建 DERP 中继(打洞失败兜底)
打洞直连失败时走 DERP 中继。公网质量差时建议自建独立 DERP。
sequenceDiagram
participant A as 设备A(NAT后)
participant B as 设备B(NAT后)
participant DER as DERP中继(公网)
A->>DER: 尝试 NAT 打洞
A-->>B: 直连成功?
alt 直连失败
A->>DER: 建立中继隧道
DER->>B: 转发加密流量
else 直连成功
A--xB: 端到端加密直连
end
8.1 嵌入式 DERP(在上面 config.yaml 已启用)
自带 STUN(UDP 3478)+ 中继,需在 derp.server 填好 ipv4 公网 IP。
8.2 独立 DERP 服务器(derper,生产推荐)
derper:
image: fredliang/derper:latest
container_name: derper
restart: unless-stopped
ports:
- "41000:41000/udp" # STUN
- "40000:40000" # DERP 主端口;若自有证书可再开需要端口
environment:
DERP_DOMAIN: "47.1.1.1" # 你的 IP 或域名
DERP_CERT_MODE: manual
DERP_CERT_DIR: /certs
DERP_ADDR: ":40000"
DERP_STUN: "true"
DERP_STUN_PORT: "41000"
DERP_HTTP_PORT: "-1"
DERP_VERIFY_CLIENTS: "true" # 防白嫖;需本地 tailscaled.sock 或 tailscale 节点
volumes:
- ./certs:/certs # 自签证书,命名 <ip>.key / <ip>.crt
headscale 侧通过 derp.paths 引用:
# /etc/headscale/derp.yaml
regions:
900:
regionid: 900
regioncode: cn-custom
regionname: China
nodes:
- name: node
regionid: 900
hostname: 47.1.1.1
ipv4: 47.1.1.1
stunport: 41000
derpport: 40000
insecurefortests: true # 自签证书时需 true
验证:客户端 tailscale netcheck 与 tailscale debug derp-map。
9. 常用管理命令速查
| 命令(docker exec headscale headscale …) | 作用 |
|---|---|
users create <name> / users list |
创建 / 列出用户分组 |
nodes list / nodes list -u <user> -t |
节点列表(带 tag) |
nodes register --user <u> --key nodekey:xxx |
手动注册节点 |
nodes delete -i <id> |
删除节点 |
nodes tag -i <id> -t tag:xxx |
给节点打标签 |
nodes expire -i <id> |
强制节点过期 |
apikeys create --expiration 999d |
创建 API Key |
preauthkeys create --user <u> --reusable --expiration 90d |
创建预认证 Key |
policy set -f policy.hujson |
数据库模式下发 ACL |
headscale configtest / tailscale netcheck |
配置校验 / 网络诊断 |
headscale debug derp-map |
查看 DERP 列表与连通 |
10. 常见问题排查
| 现象 | 处理 |
|---|---|
| 客户端登录打不开链接 | server_url 与反代域名不一致;未走 https;反代未配 WebSocket(Upgrade 头) |
| 反复要求登录 / 节点掉线 | 检查服务器时间(NTP);密钥过期;重启 systemd 服务或容器 |
| 节点已 up 但不在线 | 确认客户端 --login-server 指向正确域名;nodes list 查看在线状态 |
| DERP 连不上 | STUN 端口 udp/3478(或 41000)未放行;内核防火墙 ufw/firewalld 拦截 |
| 子网路由不通 | 客户端要 --accept-routes;路由需在控制台批准或 autoApprovers 豁免 |
| 客户端登录提示 certificate | 域名证书未配置;临时可用 talk to hostname directly 或补齐证书 |
| UI 面板登录失败 | API Key 过期(重新 apikeys create);public_url / url 填错 |
| Docker 卷权限报错 | 挂载目录 owner 需对应容器 user(如 1000:1000) |
| ACL 不生效 | Headscale 需 policy.mode: file + 正确 path,且文件语法正确 |
11. 安全建议
- 控制面必须放在反向代理后并启用 HTTPS,不要直接暴露裸 HTTP 8080。
- 默认放行改为最小权限:ACL 写"允许互访自己 + 显式放行",多用户场景务必收敛。
- 生产节点(无人值守服务器)打
tag:并关闭密钥过期,用预认证 Key 接入。 derp.server.verify_clients: true或 derper--verify-clients,防止中继被白嫖。- 定期备份数据库(SQLite 文件)与 config.yaml。
- 关注 Headscale GitHub Releases,及时升级(各版本 config 字段可能变化)。
评论交流