🛠️ 自建 Tailscale 控制服务器:Headscale + Headplane 部署完整指南

适用对象:具备 Docker 基础的运维人员或技术爱好者
核心组件:Headscale (v0.29+)、Headplane (Web UI)、Traefik (反向代理)
文档说明:本文档在保留原教程所有技术细节的基础上,优化了逻辑结构与排版,补充了关键注释,旨在作为一份专业的运维备忘录与教学手册。
隐私提示:文中所有 IP 地址、域名、密钥均已脱敏处理,请在实际操作中替换为您的实际参数。


📝 一、环境准备与规划

在开始部署前,请确保你的服务器已满足以下基础环境要求,并完成目录结构的规划。

1.1 基础环境依赖

  • Docker:已安装并运行。
  • Docker Compose:已安装(Plugin 形式或独立二进制)。
  • 域名:拥有一个公网域名(文中统一使用 example.com 及其子域名作为示例)。
  • 网络:服务器具备公网 IP 或处于可访问的内网环境中。

1.2 目录结构初始化
创建项目根目录及必要的数据存储路径。此结构便于后续的数据备份与迁移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 创建项目主目录
mkdir -p ~/headscale-stack
cd ~/headscale-stack

# 2. 创建数据子目录
mkdir -p data/headscale/{config,data} # Headscale 配置与数据(数据库/密钥)
mkdir -p data/headplane/{config,data} # Headplane 配置与会话数据

# 最终结构示意
# ~/headscale-stack/
# ├── docker-compose.yml
# └── data/
# ├── headscale/ # Headscale 配置与数据
# └── headplane/ # Headplane 配置与数据

⚙️ 二、核心组件配置

本章节包含两个核心组件的配置文件编写。请严格按照注释说明进行修改。

2.1 Headscale 服务配置 (config.yaml)

💡 原理注释server_url 必须是 HTTPS 地址,因为 Tailscale 客户端强制要求安全连接。此处的端口 5001 是对外暴露的端口,而 listen_addr 是容器内部供 Traefik 代理的端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# ============================================================
# Headscale 主配置文件
# 路径: ./data/headscale/config/config.yaml
# ============================================================

# ----- 服务地址与监听 -----
# 客户端连接的公网地址 (格式: https://<域名>:<端口>)
# 🔒 隐私处理: 域名已替换为通用示例
server_url: https://headscale.example.com:5001

# 服务监听地址 (容器内部,供 Traefik 反向代理)
listen_addr: 0.0.0.0:8080

# gRPC 监听 (用于命令行工具交互)
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false

# ----- 加密与密钥 -----
noise:
# 密钥路径,运行时自动生成
private_key_path: /var/lib/headscale/noise_private.key

# ----- IP 地址池配置 -----
# 推荐使用标准 Tailscale 范围,避免与本地网络冲突
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
allocation: sequential

# ----- DERP 中继服务器配置 (关键) -----
# 注意: 新版 Headscale 内置 DERP,无需手动维护 DERP 地图
derp:
server:
enabled: true
region_id: 999 # 区域ID (建议900-999,避免与官方冲突)
region_code: "my-derp"
region_name: "My Home DERP"
verify_clients: false # 建议关闭,避免 NAT 穿透问题
stun_listen_addr: "0.0.0.0:3478"
# 私钥路径,运行时自动生成
private_key_path: /var/lib/headscale/derp_server_private.key
# 禁用外部 DERP 源,仅使用自建
urls: []
auto_update_enabled: false

# ----- 数据库配置 (SQLite) -----
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true

# ----- TLS 证书 (由 Traefik 处理,此处留空) -----
tls_cert_path: ""
tls_key_path: ""

# ----- DNS 配置 (MagicDNS) -----
dns:
magic_dns: true
base_domain: headscale.internal # 内网解析域名
override_local_dns: true
nameservers:
global:
- 1.1.1.1
- 1.0.0.1

2.2 Headplane 管理界面配置

  • 生成密钥:执行 openssl rand -hex 16 生成 32 位随机字符串。
  • 配置文件:创建 ./data/headplane/config/config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
# ============================================================
# Headplane 管理界面配置
# 路径: ./data/headplane/config/config.yaml
# ============================================================
server:
listen_addr: 0.0.0.0:3000
# 🔒 隐私处理: 密钥已脱敏,需替换为实际生成的值
cookie_secret: "your_generated_32_byte_secret_here"

headscale:
# 注意: 这里是容器间通信地址,使用 Docker Compose 服务名
url: http://headscale:8080
data_dir: /var/lib/headplane

🐳 三、Docker Compose 部署与启动

3.1 编写 Compose 文件

⚠️ 注意事项:本配置依赖一个预先创建的外部 Docker 网络 ipbridge,以确保容器间通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ============================================================
# Docker Compose 配置文件
# 路径: ./docker-compose.yml
# ============================================================
version: '3.8'

services:
headscale:
image: headscale/headscale:latest
container_name: headscale_svc
restart: unless-stopped
command: serve
# 端口映射: STUN(UDP) 和 gRPC 必须直接映射
ports:
- "8080:8080"
- "50443:50443"
- "3478:3478/udp"
volumes:
- ./data/headscale/config:/etc/headscale:ro
- ./data/headscale/data:/var/lib/headscale
networks:
- ipbridge
# 健康检查
healthcheck:
test: ["CMD", "headscale", "status"]
interval: 30s
timeout: 10s
retries: 3

headplane:
image: ghcr.io/tale/headplane:latest
container_name: headplane_svc
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./data/headplane/config/config.yaml:/etc/headplane/config.yaml:ro
- ./data/headplane/data:/var/lib/headplane
networks:
- ipbridge
depends_on:
- headscale

networks:
ipbridge:
external: true
name: ipbridge

3.2 初始化与启动
在启动前,需确保数据目录权限正确,特别是 SQLite 数据库文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 创建外部网络 (如未创建)
docker network create ipbridge

# 2. 设置目录权限 (关键步骤,避免权限拒绝)
cd ~/headscale-stack
chmod -R 755 data
touch data/headscale/data/db.sqlite
# 🔒 隐私处理: 权限设置为通用值
chmod 666 data/headscale/data/db.sqlite

# 3. 拉取镜像并启动
docker compose pull
docker compose up -d

# 4. 查看日志确认状态
docker compose logs -f

🌐 四、反向代理与域名接入 (Traefik)

Headscale 需要 HTTPS 访问,建议使用 Traefik 进行动态代理。

4.1 Traefik 动态配置
在 Traefik 的动态配置文件(如 dynamic.yaml)中添加以下路由规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
http:
routers:
# Headscale API 路由
headscale-api:
rule: "Host(`headscale.example.com`)"
entryPoints: ["websecure"]
service: headscale-api-svc
tls:
certResolver: myresolver # 替换为你的证书解析器名称

# Headplane 管理界面 (挂载在 /admin 路径下)
headplane:
rule: "Host(`headscale.example.com`) && PathPrefix(`/admin`)"
entryPoints: ["websecure"]
service: headplane-svc
tls:
certResolver: myresolver

# DERP 中继服务 (重要: 必须单独配置,且路径为 /derp)
# 🔒 隐私处理: DERP 域名已替换为通用示例
derp:
rule: "Host(`derp.example.com`)"
entryPoints: ["websecure"]
service: derp-svc
tls:
certResolver: myresolver

services:
headplane-svc:
loadBalancer:
servers:
- url: "http://<server_ip>:3000"
# DERP 复用 Headscale 8080 端口
derp-svc:
loadBalancer:
servers:
- url: "http://<server_ip>:8080"

4.2 重载 Traefik
根据你的 Traefik 部署方式,执行重启或发送 SIGHUP 信号重载配置。


🔐 五、初始化配置与客户端接入

5.1 创建用户与密钥
进入容器执行初始化命令。注意:Headscale v0.29+ 使用用户 ID 而非用户名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 创建用户 (例如: home)
docker compose exec headscale headscale users create home

# 2. 查看用户 ID (重要,后续命令需使用 ID)
docker compose exec headscale headscale users list
# 输出示例: ID: 1, Name: home

# 3. 生成预授权密钥 (用于客户端免交互登录)
# 🔒 隐私处理: 密钥输出为示例格式,实际值需从控制台复制
docker compose exec headscale headscale preauthkeys create --user 1 --reusable --expiration 3650d

# 4. 生成 API 密钥 (用于登录 Headplane 管理界面)
# 🔒 隐私处理: API Key 为敏感信息,已脱敏
docker compose exec headscale headscale apikeys create --expiration 3650d

5.2 客户端连接命令
<auth-key> 替换为上一步生成的密钥。

平台 连接命令/方式
Linux/macOS (CLI) tailscale up --login-server=https://headscale.example.com:5001 --auth-key=<密钥> --accept-routes=true
Windows (PS) tailscale up --login-server=https://headscale.example.com:5001 --auth-key=<密钥> --accept-routes=true
OpenWrt 在 Web 界面或配置文件中设置 LoginServerAuthKey
iOS/Android 打开 App -> 设置 -> 使用自定义协调服务器 -> 输入 URL -> 登录

🛡️ 六、运维管理与故障排查

6.1 常用运维命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
# 进入项目目录
cd ~/headscale-stack

# 服务状态管理
docker compose ps
docker compose restart

# 节点管理 (列出、重命名、删除)
docker compose exec headscale headscale nodes list
docker compose exec headscale headscale nodes rename --identifier <ID> --new-name <新名称>

# 密钥管理 (查看、创建)
docker compose exec headscale headscale preauthkeys list --user 1

6.2 关键验证步骤
部署完成后,请按以下步骤验证服务是否正常:

  1. 健康检查:访问 https://headscale.example.com:5001/health,预期返回 {"status":"pass"}
  2. Headplane 登录:访问 https://headscale.example.com:5001/admin,使用 API 密钥登录。
  3. DERP 状态:在客户端执行 tailscale netcheck,观察输出中是否包含 Region 999: derp.example.com:443 且状态为 ok

6.3 常见问题 (FAQ)

  • Q: 客户端连接提示证书错误?
    • A: 如果是自签证书,客户端连接时需添加 --insecure 参数(仅限测试环境)。生产环境请确保域名证书有效。
  • Q: DERP 连接失败?
    • A: 检查防火墙是否放行 UDP 3478 端口,以及 Traefik 是否正确配置了 derp.example.com 的路由。
  • Q: Headplane 提示版本不匹配?
    • A: Partial version match 警告通常不影响核心功能,只要 Headscale 和 Headplane 保持更新即可。