彻底私有化你的内网:Headscale + 内置 DERP 全自托管 VPN 搭建指南
系列导航 本文是「自托管 VPN」系列第 1 篇,介绍完整部署流程。
架构总览
这套方案的核心思想是:用开源的 Headscale 替代 Tailscale 的官方控制服务器,所有设备元数据、路由策略都只存在于你自己的服务器上;流量走 WireGuard 直连,NAT 穿透失败时通过自建 DERP 中继兜底,全程不经过任何第三方。
| 角色 | 软件 | 运行位置 |
|---|---|---|
| 控制面 | Headscale v0.29.2 | 公网服务器(本机) |
| DERP 中继 | Headscale 内置 | 同上,/derp 路径 |
| STUN | Headscale 内置 | UDP 3478 |
| TLS 终止 | Caddy | 同上,443 端口 |
| 客户端 | tailscale(官方开源) | 各接入设备 |
为什么选内置 DERP 而非单独部署
derper? Headscale v0.22+ 已内置 DERP 服务器,和控制面共用同一进程,零额外部署成本。在同一台服务器资源紧张时是首选。如需多地 DERP 节点,再单独部署derper扩展。
一、前置条件
服务器要求
| 项目 | 最低要求 |
|---|---|
| 公网 IP | 必须,固定或动态均可 |
| 内存 | 64 MB(Headscale 本身极轻量) |
| 系统 | Linux(Debian/Ubuntu/CentOS 均可) |
| 开放端口 | TCP 443(由 Caddy 统一管理)、UDP 3478(STUN)、UDP 41641(WireGuard 直连,可选) |
DNS 配置
在你的 DNS 服务商(如 Cloudflare)添加两条 A 记录:
| 子域名 | 类型 | 值 | 说明 |
|---|---|---|---|
hs.k330.com |
A | 你的服务器公网 IP |
Headscale 控制面 + DERP |
derp.k330.com |
A | 你的服务器公网 IP |
预留,用于未来独立 DERP 节点 |
Cloudflare 用户:关闭橙色云图标(Proxy),改为 DNS only(灰色),否则 UDP STUN 流量会被 CF 拦截。
二、安装 Headscale
# 下载预编译二进制(x86_64)
curl -fsSL -o /tmp/headscale \
"https://github.com/juanfont/headscale/releases/download/v0.29.2/headscale_0.29.2_linux_amd64"
chmod +x /tmp/headscale
sudo mv /tmp/headscale /usr/local/bin/headscale
# 验证
headscale version
# 输出:headscale version v0.29.2
# 创建专用系统用户(无 shell、无家目录登录权限)
sudo useradd -r -m -d /var/lib/headscale -s /usr/bin/nologin headscale
# 创建所需目录
sudo mkdir -p /etc/headscale /var/lib/headscale /var/run/headscale
sudo chown headscale:headscale /var/lib/headscale /var/run/headscale
三、编写配置文件
主配置 /etc/headscale/config.yaml
sudo tee /etc/headscale/config.yaml > /dev/null << 'EOF'
# 客户端实际访问的 URL(必须 HTTPS,Caddy 提供 TLS)
server_url: https://hs.k330.com
# Headscale 内部监听地址(Caddy 反代到这里)
listen_addr: 127.0.0.1:8085
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: true
# 信任 Caddy 传来的 X-Forwarded-For
trusted_proxies:
- 127.0.0.1/32
noise:
private_key_path: /var/lib/headscale/noise_private.key
# VPN 网段(Tailscale 协议规定必须在此范围内)
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
allocation: sequential
# 内置 DERP 中继
derp:
server:
enabled: true
region_id: 900
region_code: cn-self
region_name: "自建中继(hs.k330.com)"
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: 104.160.46.59 # ← 改为你的服务器公网 IP
ipv6: ""
# 同时保留官方 DERP 作为备用(删除这行则纯私有)
urls:
- https://controlplane.tailscale.com/derpmap/default
auto_update_enabled: true
update_frequency: 24h
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
# ACL 策略模式:database 表示通过 Web UI / CLI 管理,存入 SQLite
# file 模式则从 policy.hujson 文件读取(只读,无法通过 Headplane 编辑)
policy:
mode: database
# MagicDNS 配置(base_domain 必须与 server_url 不同)
dns:
magic_dns: true
base_domain: ts.k330.com
override_local_dns: true
nameservers:
global:
- 1.1.1.1
- 8.8.8.8
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"
# 禁止 Tailscale 节点上报日志到 Tailscale Inc.
logtail:
enabled: false
taildrop:
enabled: true
auto_update:
enabled: false
EOF
初始 ACL 策略(CLI 导入)
使用 database 模式时,策略存储在 SQLite 里,通过 CLI 或 Headplane Web UI 管理。
先创建初始策略文件:
sudo tee /etc/headscale/policy.hujson > /dev/null << 'EOF'
{
"acls": [
{ "action": "accept", "src": ["*"], "dst": ["*:*"] }
]
}
EOF
sudo chown -R headscale:headscale /etc/headscale
再导入到数据库:
sudo headscale policy set -f /etc/headscale/policy.hujson
# Policy updated.
# 查看当前策略
sudo headscale policy get
之后可直接在 Headplane Web UI(
https://hp.k330.com/admin)里可视化编辑 ACL,无需操作文件。
四、配置 Caddy 反向代理
在现有 Caddyfile 末尾追加:
hs.k330.com {
# 反代到 Headscale(含 DERP WebSocket 流量)
reverse_proxy localhost:8085 {
flush_interval -1
transport http {
read_timeout 300s
write_timeout 300s
}
}
}
然后重载 Caddy:
sudo systemctl reload caddy
# 或
sudo caddy reload --config /home/sdmike/Caddyfile
为什么 DERP 不需要单独的子域? 内置 DERP 通过
/derp路径挂载在hs.k330.com上,Caddy 的flush_interval -1确保 WebSocket 长连接不被截断。
五、创建 systemd 服务
sudo tee /etc/systemd/system/headscale.service > /dev/null << 'EOF'
[Unit]
Description=Headscale VPN Control Plane
Documentation=https://headscale.net
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=headscale
Group=headscale
RuntimeDirectory=headscale
RuntimeDirectoryMode=0750
ExecStart=/usr/local/bin/headscale serve
Restart=on-failure
RestartSec=5
# 限制资源占用
LimitNOFILE=65536
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now headscale
sudo systemctl status headscale
查看日志确认启动正常:
journalctl -u headscale -f --no-pager
# 期望看到:headscale is running
六、防火墙放行端口
# UFW(Ubuntu/Debian)
sudo ufw allow 3478/udp comment "STUN for Headscale"
sudo ufw allow 41641/udp comment "WireGuard direct" # 可选,提升直连成功率
# iptables(如果使用)
sudo iptables -A INPUT -p udp --dport 3478 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 41641 -j ACCEPT
# Oracle Cloud / AWS 用户还需在控制台安全规则里放行以上 UDP 端口
七、首次配置:创建用户与注册设备
Headscale 通过"用户(user)"组织设备,类似 Tailscale 的账号。
# 创建用户
sudo headscale users create admin
# 查看用户列表
sudo headscale users list
# 生成预授权密钥(24小时有效,可重复使用)
# 注意:v0.29.x 的 --user 参数需传用户 ID(数字),不是用户名
sudo headscale preauthkeys create --user 1 --reusable --expiration 24h
# 输出类似:hskey-auth-xxxxxxxxxxxxxxxx
八、客户端接入
所有平台都使用官方 Tailscale 客户端,只是把控制服务器地址指向自建的 https://hs.k330.com。
先在服务端生成预授权密钥(auth key),接入时用它代替网页登录:
sudo headscale preauthkeys create --user 1 --reusable --expiration 24h # 输出:hskey-auth-xxxxxxxxxxxxxxxx(保存好,下面各平台都要用)
macOS
下载:tailscale.com/download/mac(App Store 或直装包)
安装后打开 Tailscale,不要点"登录",改用命令行:
# 连接到自建 Headscale
sudo tailscale up \
--login-server https://hs.k330.com \
--auth-key hskey-auth-你的密钥 \
--accept-dns=true \
--accept-routes=true
# 验证
tailscale status
tailscale ip -4 # 输出 VPN IP,如 100.64.0.2
Windows
下载:tailscale.com/download/windows
安装后不要用界面登录,打开 PowerShell(管理员)执行:
tailscale up `
--login-server https://hs.k330.com `
--auth-key hskey-auth-你的密钥 `
--accept-dns=true `
--accept-routes=true
# 验证
tailscale status
tailscale ip -4
如果提示找不到
tailscale命令,路径通常在C:\Program Files\Tailscale\tailscale.exe,加到 PATH 或用完整路径执行。
Linux
# 一行安装(支持 Debian / Ubuntu / Fedora / Arch 等)
curl -fsSL https://tailscale.com/install.sh | sh
# 连接
sudo tailscale up \
--login-server https://hs.k330.com \
--auth-key hskey-auth-你的密钥 \
--accept-dns=true \
--accept-routes=true
# 验证
tailscale status
iOS
下载:App Store 搜索 Tailscale,或直达链接:apps.apple.com/app/tailscale/id1470499037
- 打开 App → 右上角头像 → Log in with custom server
- 输入控制服务器地址:
https://hs.k330.com,点 Connect - App 会显示一个 Node Key(格式如
nodekey:xxxxxx) - 在服务端注册该设备:
sudo headscale nodes register --user 1 --key nodekey:xxxxxx - 回到 App,设备即接入 VPN
如果你用的是预授权密钥,步骤 3-4 可以跳过——App 里直接填 auth key 即可(位置:Log in with custom server → 展开 Advanced 选项 → Auth key)。
Android
下载:Google Play 搜索 Tailscale,或直达:play.google.com/store/apps/details?id=com.tailscale.ipn
无 Google Play 的设备可下载 APK:pkgs.tailscale.com/stable/#android
操作步骤与 iOS 相同:登录时选择自定义服务器 → 填入 https://hs.k330.com。
九、验证连接
# 查看所有已注册设备
sudo headscale nodes list
# 在任意两台设备间测试延迟
ping 100.64.0.2
# 查看当前连接路径(直连 or 中继)
tailscale ping 100.64.0.2
# 输出含 "via DERP" 表示走中继,无此字样表示直连
# 查看 DERP 地图(确认自建节点在列)
tailscale netcheck
十、日常管理速查
# 撤销设备
sudo headscale nodes delete --identifier <节点ID>
# 轮转预授权密钥(旧密钥失效)
sudo headscale preauthkeys expire --key <key>
# 查看路由(子网路由器功能)
sudo headscale routes list
# 启用某设备的子网路由(让整个内网可达)
sudo headscale routes enable --route <routeID>
# 重启服务
sudo systemctl restart headscale
# 备份数据库(所有设备注册信息在此)
sudo cp /var/lib/headscale/db.sqlite ~/headscale-backup-$(date +%Y%m%d).sqlite
进阶:启用 Exit Node(全局 VPN 出口)
将某台设备设为出口,让其他设备的所有流量从该机器出去:
# 在要做出口的设备上
sudo tailscale up --advertise-exit-node
# Headscale 服务端审批路由
sudo headscale routes list
sudo headscale routes enable --route <exit-route-id>
# 在其他设备上使用该出口
sudo tailscale up --exit-node=100.64.0.X
进阶:ACL 访问控制与用户隔离
默认策略允许所有设备互通,生产环境建议按角色划分用户并收紧权限。
用户规划
Headscale 通过"用户(user)"组织设备,先按角色建好用户,再给每个用户发专属 auth key:
sudo headscale users create share # 共享设备(NAS、服务器等,对全员可见)
sudo headscale users create user # 普通用户(只能访问自己的设备)
sudo headscale users create guest # 访客(只能看到 guest 组内的其他访客设备)
sudo headscale users list
# 输出:1=admin 2=share 3=user 4=guest
# 按用户 ID 生成专属 auth key
sudo headscale preauthkeys create --user 2 --reusable --expiration 24h # share
sudo headscale preauthkeys create --user 3 --reusable --expiration 24h # user
sudo headscale preauthkeys create --user 4 --reusable --expiration 24h # guest
ACL 策略
注意:Headscale v0.29.x 的 policy 格式中,用户名必须带
@后缀(如share@)。
sudo headscale policy set -f - << 'EOF'
{
"groups": {
"group:shared": ["share@"],
"group:users": ["user@"],
"group:guests": ["guest@"]
},
"acls": [
// share 组的设备:所有人都能访问
{ "action": "accept", "src": ["autogroup:member"], "dst": ["group:shared:*"] },
// 每个用户只能访问自己的设备
{ "action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self:*"] },
// guest 组内部互通(访客之间可以互相访问)
{ "action": "accept", "src": ["group:guests"], "dst": ["group:guests:*"] }
]
}
EOF
效果汇总:
| 用户 | 能访问 share | 能访问 admin | 能访问其他 user | 能访问同组 guest |
|---|---|---|---|---|
| admin | ✓ | ✓(自己) | ✗ | ✗ |
| share | ✓ | ✗ | ✗ | ✗ |
| user | ✓ | ✗ | ✗ | ✗ |
| guest | ✓ | ✗ | ✗ | ✓ |
查看与更新策略
# 查看当前策略
sudo headscale policy get
# 通过文件更新
sudo headscale policy set -f /etc/headscale/policy.hujson
使用 Headplane(
https://hp.k330.com/admin)可在 Web 界面直接编辑 ACL,无需命令行。
进阶:安装 Headplane Web UI
Headscale 本身没有 Web 界面,Headplane 是目前功能最完整的第三方管理 UI,支持设备管理、ACL 编辑、DNS 配置等。
前置:为 Caddy 添加 hp.k330.com
hp.k330.com {
reverse_proxy localhost:3001 {
flush_interval -1
transport http {
read_timeout 120s
write_timeout 120s
}
}
}
构建与安装
Headplane 需要 Go ≥ 1.25 和 Node.js 22 + pnpm:
# 安装 pnpm(如果没有)
npm install -g pnpm@10
# 克隆并构建
git clone --depth=1 --branch v0.6.3 https://github.com/tale/headplane.git /home/你的用户名/headplane
cd /home/你的用户名/headplane
pnpm install
pnpm build
生成 Headscale API Key
sudo headscale apikeys create --expiration 365d
# 输出:hskey-api-xxxxxxxxxxxx
配置文件 /etc/headplane/config.yaml
sudo mkdir -p /etc/headplane
sudo tee /etc/headplane/config.yaml > /dev/null << 'EOF'
server:
host: "127.0.0.1"
port: 3001
base_url: "https://hp.k330.com"
cookie_secret: "填入32位随机字符串" # openssl rand -hex 16
cookie_secure: true
data_path: "/var/lib/headplane"
headscale:
url: "http://127.0.0.1:8085"
config_path: "/etc/headscale/config.yaml"
api_key: "hskey-api-你的密钥"
EOF
sudo mkdir -p /var/lib/headplane
让 Headplane 运行用户能写 Headscale 配置:
sudo usermod -aG headscale 你的用户名 sudo chmod g+rw /etc/headscale/config.yaml /etc/headscale/policy.hujson
systemd 服务
sudo tee /etc/systemd/system/headplane.service > /dev/null << 'EOF'
[Unit]
Description=Headplane UI for Headscale
After=headscale.service
Requires=headscale.service
[Service]
Type=simple
User=你的用户名
WorkingDirectory=/home/你的用户名/headplane
ExecStart=/bin/bash -c 'source ~/.nvm/nvm.sh && node /home/你的用户名/headplane/build/server/index.js'
Restart=on-failure
RestartSec=5
Environment=HEADPLANE_CONFIG_PATH=/etc/headplane/config.yaml
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now headplane
访问 https://hp.k330.com/admin,用生成的 API Key 登录。
实操清单
- DNS 添加
hs.k330.comA 记录,指向服务器公网 IP - (可选)DNS 添加
derp.k330.comA 记录,备用 - (可选)DNS 添加
hp.k330.comA 记录,用于 Headplane UI - Cloudflare 用户:关闭以上子域的橙色云(改为 DNS only)
- 下载 Headscale 二进制到
/usr/local/bin/headscale - 创建
headscale系统用户及目录 - 写入
/etc/headscale/config.yaml(policy.mode: database) - 在
Caddyfile追加hs.k330.com和hp.k330.com块,重载 Caddy - 写入
/etc/systemd/system/headscale.service,启用并启动 - 防火墙放行 UDP 3478 和 UDP 41641
-
headscale users create admin创建用户 -
headscale preauthkeys create --user 1生成预授权密钥 -
headscale policy set -f policy.hujson写入 ACL(按角色隔离:share 全局可见,user 自见,guest 组内互通) - 构建并启动 Headplane,访问
https://hp.k330.com/admin - 服务器本机接入(VPN IP:
100.64.0.1,主机名:server-k330) - 其他设备接入:手机 / 电脑运行
tailscale up --login-server https://hs.k330.com --auth-key <key> -
tailscale ping验证设备间连通性 -
tailscale netcheck确认自建 DERP 节点已被识别 - 备份
/var/lib/headscale/db.sqlite