Tailscale 是一种优秀的异地组网工具,支持通过 STUN 打洞实现客户端点对点直连,互联协议基于 WireGuard 实现,兼具高效与安全的特性。相关项目的开源代码库请参考 Tailscale GitHub Repository。
简单而言,Tailscale 可以不受限于服务器带宽,使位于不同网络环境下的设备获得类似于同一局域网下的体验。但在国内网络环境下,Tailscale 官方的 DERP 中转服务器多部署在海外,详情可参考 High Availability Documentation。一旦设备之间点对点(P2P)直连打洞失败,流量就需要绕道海外的中转节点进行中转,导致网络连接非常不稳定。
为了保障隐私和使用稳定性,决定采用自建方案搭建国内的虚拟局域网。本文将以步骤化的方式,介绍如何使用 1Panel 与 Docker 部署自托管的 Headscale 服务器(官网:Headscale Official Website),并结合国内的 DERP 中转服务器实现稳定的低延迟异地组网。
第一步:网络组网原理与前期准备
1. 为什么需要自建 Headscale 服务器与中转服务?
Tailscale 的服务器端主要分为两部分:
- Headscale 服务器(服务端) :负责节点的分配、通信与认证,其工作模式细节可以参考 Control and Data Planes 官方文档。
- DERP 中转服务器(中转站) :当两个节点无法通过 STUN 打洞直连时,通过中转服务器转发流量。自建国内中转节点能确保打洞失败时依然拥有低延迟。
2. NAT 网络类型对直连的影响
在国内的家庭及办公网络中,NAT 类型主要为 Full Cone(NAT1)、Port Restricted Cone(NAT3)和 Symmetric(NAT4)。
- 如果两端 NAT 类型在 NAT3 及以上 ,通常可以顺利建立 P2P 直连隧道。
- 如果一端为 NAT4(对称型) ,由于其端口分配机制严格,通常无法打通直连,此时流量必须完全通过 DERP 服务器进行中转。
- IPv6 优化直连 :IPv6 具有去 NAT 化的天然优势。如果自建的 DERP 节点配置了 IPv6 地址,且客户端也同样具有 IPv6 地址,则可以通过 IPv6 直接实现 STUN 打洞直连,大幅提升网络互联质量。
第二步:使用 Docker 部署 Headscale 服务器
Headscale 是 Tailscale 协调服务端的开源实现。容器的详细部署说明可参考 Headscale Container Installation Guide。
1. 编写 Docker Compose 文件
在 1Panel 应用管理或目标服务器工作目录下,建立 docker-compose.yml 配置文件:
services:
headscale:
image: docker.io/headscale/headscale:latest
restart: always
container_name: headscale
read_only: true
tmpfs:
- /var/run/headscale
ports:
- "8080:8080" # Headscale 服务主端口
- "9090:9090" # Metrics 运行报告端口
volumes:
- ./config:/etc/headscale:ro
- ./lib:/var/lib/headscale
command: serve
healthcheck:
test: ["CMD", "headscale", "health"]
interval: 10s
timeout: 5s
retries: 3
2. 配置 Headscale 参数(config.yaml)
在挂载的 ./config 目录下创建主配置文件 config.yaml。您可以基于官方给出的 Headscale Official Example Config 进行修改,核心参数如下:
server_url: https://headscale.example.com # 客户端连接的 Headscale 服务域名
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
noise:
# 用于 Noise 加密协议的私钥路径
private_key_path: /var/lib/headscale/noise_private.key
prefixes:
# 虚拟局域网的内网 IP 分配段
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
allocation: sequential
derp:
# 关闭内置 DERP 服务,我们使用后续独立的高性能 DERP 容器
server:
enabled: false
# 从官方 URL 引入中转节点映射表
urls:
- https://controlplane.tailscale.com/derpmap/default
# 从本地配置文件引入自定义的 DERP 服务器
paths:
- /etc/headscale/derp.yaml
auto_update_enabled: true
update_frequency: 3h
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true # 开启 SQLite WAL 模式以提升数据库并发性能
wal_autocheckpoint: 1000
dns:
magic_dns: true # 开启虚拟机局域网 MagicDNS
base_domain: internal.net # 虚拟网根域名
override_local_dns: true
nameservers:
global:
- 223.5.5.5 # 公共 DNS 设置
- 119.29.29.29
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"
注:关于内置 DERP 服务的开启细节与参数解释,可参考 Headscale Embedded DERP Configuration 。
第三步:部署可视化管理页面(Headscale-UI)
为了避免频繁在命令行维护节点和用户,推荐部署官方支持的可视化前端面板 Headscale-UI 项目。详情请见 Headscale Web-UI Integration Guide。
在刚才的 docker-compose.yml 中追加以下服务:
headscale-ui:
image: ghcr.io/gurucomputing/headscale-ui:latest
restart: always
container_name: headscale-ui
ports:
- "8081:8080" # 映射外部 8081 端口以避免占用 8080
第四步:自建国内独立的 DERP 中转服务器
对于独立的自建 DERP 服务器,为了方便维护,我们选择使用第三方 Docker 镜像。构建方案及命令行参数可参考 Tailscale Custom DERP Server Reference 官方文档以及 Sparanoid DERP Docker Image。
1. 编写 DERP 服务docker compose
services:
derp:
image: sparanoid/derp:master
restart: always
init: true
ports:
- "80:80"
- "443:443" # DERP/HTTPS 协议端口
- "3478:3478/udp" # STUN 服务的 UDP 端口
volumes:
- ./ssl:/app/certs # 挂载您的域名 SSL 证书
- /var/run/tailscale:/var/run/tailscale
command: sh -c "
derper \
-hostname derp.example.com \
-certdir /app/certs \
-certmode manual \
-verify-clients=true \
-verify-client-url=https://headscale.example.com/verify \
-verify-client-url-fail-open=true"
2. 客户端验证参数设置(防止他人薅羊毛)
为了防止自建中转服务被他人匿名蹭网,可以使用参数来指定验证服务器:
-verify-client-url(验证客户端 URL 参数):
将其指向您的 Headscale 服务器验证接口(例如 https://headscale.example.com/verify)。当中转服务器收到客户端连接请求时,会向 Headscale 确认该客户端是否属于您注册的节点。如果确认属于合法设备则允许接入,否则直接拒绝。
-verify-client-url-fail-open(故障降级开路):
用于控制当 Headscale 服务器暂时无法访问时的中转策略。设置为 true 时,若校验接口连接失败,中转服务器将默认允许客户端继续连接以维持网络可用性;设置为 false,则在无法联系 Headscale 服务时拒绝新的连接。
更多中转控制设计详见 Headscale 官方中转文档。
3. 配置本地中继节点映射表
在 Headscale 配置文件夹下创建 derp.yaml,以便将您自建的独立中转站告知其他设备:
regions:
901:
regionid: 901
regioncode: "custom-derp"
regionname: "Custom Private DERP"
nodes:
- name: "derp-node-01"
regionid: 901
hostname: "derp.example.com"
ipv4: "x.x.x.x" # 您的 DERP 服务器公网 IPv4
ipv6: "y:y:y::y" # 您的 DERP 服务器公网 IPv6(可选)
stunport: 3478
stunonly: false
derpport: 443
第五步:配置 Nginx 反向代理与跨域设置
由于自建的管理页面 Headscale-UI 运行在您的本地浏览器中,它通过浏览器向 Headscale API 发起连接。为了确保两者正常通讯,且避免跨域(CORS)问题,如果您在 1Panel 的 Nginx 反向代理配置中绑定了 Headscale 服务,请务必添加以下规则:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
# 1. 允许跨域的配置(请将地址替换为 Headscale-UI 的公网访问源地址)
add_header 'Access-Control-Allow-Origin' 'https://headscale-ui.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
# 2. 拦截并处理浏览器的 OPTIONS 预检请求,直接返回 204
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://headscale-ui.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
第六步:在不同客户端安装并连接自建服务器
各系统所需的客户端安装包可从 Tailscale Official Download Page 进行下载。客户端的基本接入指引详见 Headscale Getting Started Guide。
1. Windows / Linux / macOS 连接自建服务器
安装客户端后,打开命令行,执行登录命令引导注册:
# 作为独立设备接入
tailscale up --login-server=https://headscale.example.com --accept-dns=true --accept-routes=true
2. 广播子网路由(外网直接访问家里/公司网段内其他设备)
如果您希望该设备充当网关,将内网段(如 192.168.1.0/24)路由到自建虚拟局域网:
tailscale up --login-server=https://headscale.example.com --exit-node-allow-lan-access --advertise-routes=192.168.1.0/24 --accept-dns=true --accept-routes=true
注:发布宣告后,需登录 Headscale-UI 网页管理端,在 Routes 页面中手动 Approve(通过)该子网广播。
3. OpenWRT 路由器等边缘端设备内存调优
由于部分网关设备内存较小,而 Go 编写的客户端在复杂互联环境下容易消耗过多系统内存,可通过设置环境变量来约束 Tailscale 使用的内存上限:
# 约束 Go 进程堆内存分配
export GOMEMLIMIT=100MiB
tailscale up --login-server=https://headscale.example.com --accept-routes=true
4. 手机端接入
- Android / iOS :下载安装客户端后,依次进入高级菜单并配置 “Alternate Server” 地址为
https://headscale.example.com,完成授权后即可无交互注册。
第七步:常用运维管理命令与连接测试速查
以下为日常管理中,在 Headscale 容器内运行的常见维护命令(可使用 docker exec):
1. 创建命名空间(用户)与密钥生成
# 1. 创建新用户
docker exec headscale headscale users create <username>
# 2. 生成多端可复用的免交互预授权 Key(设置有效期为 10 年)
docker exec headscale headscale preauthkeys create --user <username> --expiration 87600h --reusable
# 3. 创建用于 Web 界面登录的长期 API Key
docker exec headscale headscale apikeys create --expiration 87600h
2. 测试和诊断网络连接方式
要查看客户端之间是实现了低延迟点对点直连还是走自建 DERP 中转转发,可在客户端运行以下命令:
# 查看各个客户端节点之间的直连通路(显示 direct 为 P2P 打洞直连,derp 则为走中转服务器)
tailscale status --peers
# 执行网络测速与 STUN 状态检查,获取和当前各个中转节点的延迟
tailscale netcheck
通过上述命令,您可以持续监控自建私有网络的质量并对路由配置进行微调。
参考文章:
https://cloud.tencent.com/developer/article/2457464
https://blog.csdn.net/weixin_47540149/article/details/157373807