Headscale Docker 容器化部署方案
# Headscale Docker 容器化部署方案
基于 Docker Compose 容器化部署 Headscale v0.29.1,集成自建 DERP 中继和 Let's Encrypt TLS 证书,支持证书自动续期。
# 架构概览
┌──────────────────────────────────────────────────────────┐
│ 阿里云 ECS (Docker) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ headscale container │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────────────┐ │ │
│ │ │ API :443 │ │ DERP :443 + STUN │ │ │
│ │ │ (HTTPS) │ │ :3478 │ │ │
│ │ └──────┬───────┘ └──────────┬───────────┘ │ │
│ │ │ │ │ │
│ │ ┌──────┴───────┐ ┌──────────┴───────────┐ │ │
│ │ │ gRPC :50443 │ │ Let's Encrypt TLS │ │ │
│ │ └──────────────┘ └──────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ Tailscale 隧道 │
│ ┌─────────────┼─────────────┐ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │ Node A │ │ Node B │ │ Node C │ │
│ │(服务器) │ │ (手机) │ │(笔记本) │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└──────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 前置条件
| 项目 | 要求 |
|---|---|
| 服务器 | 阿里云 ECS,固定公网 IP |
| 操作系统 | Ubuntu 20.04+ / CentOS 7+ |
| Docker | v20.10+,Docker Compose v2+ |
| TLS 证书 | Let's Encrypt 可信证书(acme.sh 自动续期) |
| 安全组 | 443/TCP、50443/TCP、3478/UDP 已放行 |
# 端口规划
| 端口 | 协议 | 用途 | 说明 |
|---|---|---|---|
| 443 | TCP | Headscale API + DERP | HTTPS API 和 DERP 中继共享同一端口 |
| 50443 | TCP | gRPC 控制面 | 节点注册和管理 |
| 3478 | UDP | STUN | NAT 穿透探测,用于发现公网地址 |
# 部署步骤
# 1. 创建目录结构
sudo mkdir -p /opt/headscale/{config,data,certs}
cd /opt/headscale
1
2
2
# 2. 生成私钥
Headscale 需要三把私钥,首次部署前必须生成:
# WireGuard 私钥
docker run --rm headscale/headscale:v0.29.1 \
headscale generate private-key > /opt/headscale/data/private.key
# Noise 协议私钥(Tailscale v2 认证)
docker run --rm headscale/headscale:v0.29.1 \
headscale generate private-key > /opt/headscale/data/noise_private.key
# DERP 服务私钥
docker run --rm headscale/headscale:v0.29.1 \
headscale generate private-key > /opt/headscale/data/derp_server_private.key
# 设置权限
chmod 600 /opt/headscale/data/*.key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3. 配置文件
创建 /opt/headscale/config/config.yaml:
# ============================================================
# Headscale v0.29.1 容器化配置
# 替换 <YOUR_PUBLIC_IP> 为服务器公网 IP
# ============================================================
# ---- 基础配置 ----
server_url: https://<YOUR_PUBLIC_IP>:443
listen_addr: 0.0.0.0:443
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false
metrics_listen_addr: ""
randomize_client_port: false
disable_check_updates: true
# ---- 密钥路径(容器内路径) ----
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
# ---- 数据库 ----
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true
# ---- IP 分配 ----
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
allocation: random
# ---- DERP 中继 ----
derp:
server:
enabled: true
region_id: 999
region_code: "custom-derp"
region_name: "自建 DERP"
port: 443
stun_port: 3478
stun_listen_addr: "0.0.0.0:3478"
private_key_path: /var/lib/headscale/derp_server_private.key
tls_cert_path: /etc/headscale/certs/cert.pem
tls_key_path: /etc/headscale/certs/key.pem
ipv4: <YOUR_PUBLIC_IP>
automatically_add_embedded_derp_region: true
verify_clients: false
urls: []
paths: []
auto_update: false
# ---- 策略 ----
policy:
mode: database
path: /etc/headscale/acl.json
# ---- DNS ----
dns:
magic_dns: false
nameservers:
global:
- 223.5.5.5
- 8.8.8.8
domains: []
# ---- 日志 ----
log:
format: text
level: info
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
66
67
68
69
70
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
66
67
68
69
70
配置要点说明:
| 配置项 | 说明 |
|---|---|
server_url | 客户端连接地址,使用 HTTPS + 443 端口 |
listen_addr | API 和 DERP 共享 443 端口(同一进程) |
grpc_listen_addr | gRPC 独立端口 50443 |
database.type: sqlite | v0.29.1 字段名(旧版 db_type: sqlite3 已废弃) |
policy.mode: database | ACL 策略格式,v0.29.1 重构后的新写法 |
derp.server.tls_cert_path | DERP 使用 Let's Encrypt 证书 |
prefixes | 必须配置,否则节点无法分配 IP |
# 4. ACL 策略
创建 /opt/headscale/config/acl.json,默认全部互通:
{
"acls": [
{ "action": "accept", "src": ["*"], "dst": ["*:*"] }
]
}
1
2
3
4
5
2
3
4
5
生产环境建议按需限制节点间访问权限。
# 5. Docker Compose
创建 /opt/headscale/docker-compose.yml:
version: '3.8'
services:
headscale:
image: headscale/headscale:v0.29.1
container_name: headscale
restart: unless-stopped
ports:
- "443:443" # API + DERP
- "50443:50443" # gRPC 控制面
- "3478:3478/udp" # STUN
volumes:
- ./config:/etc/headscale:ro # 配置文件(只读)
- ./data:/var/lib/headscale # 数据持久化(DB + 密钥)
- /path/to/certs:/etc/headscale/certs:ro # TLS 证书(只读)
command: ["serve"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意事项:
command: ["serve"]— entrypoint 已包含headscale,只需serve参数./data卷挂载保证容器重建后数据不丢失(数据库 + 三把私钥)- 证书目录替换为实际路径
# 6. 启动服务
cd /opt/headscale
# 启动
docker compose up -d
# 查看日志确认无报错
docker compose logs -f headscale
1
2
3
4
5
6
7
2
3
4
5
6
7
启动成功的标志日志:
DERP region 999 started
listening on 0.0.0.0:443
gRPC server listening on 0.0.0.0:50443
1
2
3
2
3
# 7. ACL 策略加载
配置中使用 policy.mode: database 模式,需要手动将策略推入数据库:
docker exec headscale headscale policy set /etc/headscale/acl.json
1
验证:
docker exec headscale headscale policy get
1
# 8. 创建用户
# 创建用户
docker exec headscale headscale users create default
# 生成预授权密钥(有效期 30 天,可重复使用)
docker exec headscale headscale preauthkeys create \
--user 1 --reusable --expiration 720h
1
2
3
4
5
6
2
3
4
5
6
输出的密钥格式为 hskey-auth-xxx...,后续客户端连接需要使用。
# 9. 客户端连接
# 安装 Tailscale 客户端
curl -fsSL https://tailscale.com/install.sh | sh
# 连接到自建 Headscale
tailscale up \
--login-server https://<YOUR_PUBLIC_IP>:443 \
--authkey <PREAUTH_KEY> \
--hostname <DEVICE_NAME> \
--accept-routes \
--reset
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
关键说明:
- 使用
https://连接(DERP 使用可信证书,无需--derp-insecure) --reset用于清除之前的连接状态--accept-routes接受子网路由
# 10. 验证 DERP 中继
# 检查网络状态
tailscale netcheck
# 验证 DERP 连通性
tailscale ping <对端节点>
1
2
3
4
5
2
3
4
5
输出应包含自建 DERP 区域信息。
# 证书自动续期
# 问题
Let's Encrypt 证书通常 90 天有效,acme.sh 会自动续期并 reload nginx,但不会自动重启 Docker 容器。
# 解决方案
配置 acme.sh 的续期钩子,在证书更新后自动重启 headscale 容器:
# 查看当前证书配置
acme.sh --list
# 更新续期钩子(替换为实际域名或 IP)
acme.sh --install-cert -d <YOUR_DOMAIN_OR_IP> \
--reloadcmd "nginx -t && systemctl reload nginx && docker restart headscale"
1
2
3
4
5
6
2
3
4
5
6
# 续期流程
acme.sh cron (每日 07:01)
→ 检测证书即将到期(7天短期证书约4天续期)
→ 重新签发证书
→ 安装到证书目录
→ 执行续期钩子:
1. nginx -t # 验证 nginx 配置
2. systemctl reload nginx # 重载 nginx
3. docker restart headscale # 重启容器加载新证书
→ 容器启动时自动加载新证书
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 数据持久化
/opt/headscale/
├── config/
│ ├── config.yaml # Headscale 配置
│ └── acl.json # ACL 策略
├── data/
│ ├── db.sqlite # SQLite 数据库(用户、节点、密钥)
│ ├── private.key # WireGuard 私钥
│ ├── noise_private.key # Noise 协议私钥
│ └── derp_server_private.key # DERP 服务私钥
├── certs/ -> /etc/nginx/ssl/ # TLS 证书(符号链接或挂载)
└── docker-compose.yml # 容器编排
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
重建容器不会丢失数据:
./data卷持久化数据库和私钥./config卷持久化配置- 证书通过宿主机目录挂载,续期后自动生效
# 常用维护命令
# ---- 用户管理 ----
docker exec headscale headscale users list
docker exec headscale headscale users create <用户名>
# ---- 节点管理 ----
docker exec headscale headscale nodes list
echo "y" | docker exec headscale headscale nodes delete --identifier <ID>
docker exec headscale headscale nodes expire --identifier <ID>
# ---- 预授权密钥 ----
docker exec headscale headscale preauthkeys create \
--user <USER_ID> --reusable --expiration 720h
# ---- API 密钥(供 Scale Manager 等 App 使用)----
docker exec headscale headscale apikeys create --expiration 90d
# ---- ACL 策略 ----
docker exec headscale headscale policy get
docker exec headscale headscale policy set /etc/headscale/acl.json
# ---- 容器管理 ----
docker compose logs -f headscale # 实时日志
docker restart headscale # 重启
docker compose down && docker compose up -d # 重建
# ---- 客户端 ----
tailscale status # 查看连接状态
tailscale netcheck # 检查 DERP 连通性
tailscale ping <对端节点> # 测试延迟
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
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
# v0.23.0 → v0.29.1 迁移要点
如果从旧版本迁移,注意以下字段变更:
| 旧版 (v0.23.0) | 新版 (v0.29.1) | 说明 |
|---|---|---|
db_type: sqlite3 | database.type: sqlite | 数据库配置重构 |
db_path: ... | database.sqlite.path: ... | 同上 |
acl_policy_path: ... | policy.mode: database + policy.path: ... | ACL 格式重构 |
dns_config: | dns: | 字段重命名 |
| 缺失 | noise.private_key_path | 必需字段 |
| 缺失 | prefixes | IP 分配范围,必需 |
| 缺失 | derp.server.stun_listen_addr | STUN 监听地址,必需 |
command: ["headscale", "serve"] | command: ["serve"] | entrypoint 已含 headscale |
# 安全说明
| 层级 | 加密方式 | 说明 |
|---|---|---|
| DERP 中继 | TLS 1.3 | Let's Encrypt 证书,客户端信任链完整 |
| WireGuard 隧道 | ChaCha20-Poly1305 | 节点间数据端到端加密 |
| STUN | 无加密 | 仅用于 NAT 发现,不传输业务数据 |
| API | HTTPS | Let's Encrypt 证书保护 |