自建 DERP 中继:Tailscale 与 Headscale 教程
在外面用 Roamtty 连家里或公司的服务器时,Tailscale 会先尝试让手机和服务器直接连通;直连失败,流量就改由 DERP 中继转发。官方 DERP 节点都在海外,在中国大陆延迟常达 200–500 ms,打一个字要等半秒才回显。在离你近的服务器上自建 DERP,中继延迟可以降到几十毫秒。
本文先部署官方的 derper,再分别接入 Tailscale 官方服务和自建 Headscale。命令按写作时的最新版本核对:Tailscale 1.104、Headscale 0.29.5。
DERP 是什么
- Tailscale 设备之间优先用 UDP 直连(NAT 打洞)。两端都在运营商级 NAT、对称 NAT 或严格的防火墙后面时,打洞会失败,手机用蜂窝网络时很常见。
- 这时流量经 DERP 中继转发。DERP 走 HTTPS,只转发 WireGuard 加密后的数据包,中继看不到内容,也无法冒充你的设备。
- 同一个
derper进程还提供 STUN(UDP 3478),帮设备发现自己的公网地址,近处的 STUN 也有助于直连成功。
判断当前是否走中继:在服务器上运行 tailscale status,对端那一行显示 relay "tok" 表示经东京中继,显示 direct 198.51.100.7:41641 则是直连。
选择方案
| 你在用 | 做法 |
|---|---|
| Tailscale 官方账号 | 部署 derper,再在策略文件中添加 derpMap |
| 自建 Headscale,中继放在同一台 VPS | 开启 Headscale 内置 DERP,不用另装 derper |
| 自建 Headscale,中继放在另一台服务器 | 部署 derper,再写入 derp.yaml |
准备工作
- 一台有公网 IP 的服务器,网络上离你常去的地方近(国内云厂商或香港)。1 核 1 GB 足够,SSH 和 mosh 的流量很小。不要放在负载均衡、反向代理或 CDN 后面。
- 一个域名,A 记录指向这台服务器,下文用
derp.example.com、公网 IP 用203.0.113.10代替。DNS 托管在 Cloudflare 时设为“仅 DNS”(灰色云),DERP 经 CDN 代理会连不上。没有域名见直接用 IP。 - 放行端口:TCP 443(DERP)、TCP 80(申请证书和网络检测)、UDP 3478(STUN),并允许 ICMP。云厂商的安全组和系统防火墙都要放行。
# Ubuntu / Debian 用 ufw 时
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 3478/udp
中国大陆的服务器:未备案的域名访问 80 / 443 端口会被云厂商拦截,请先看国内服务器一节,改用高位端口。
部署 derper
接入 Tailscale 官方服务和 Headscale 外置中继都用这一节,以 Ubuntu / Debian 为例。
1. 编译安装
derper 没有预编译包,用 Go 编译。ARM 服务器把下面的 amd64 换成 arm64;在国内把 go.dev 换成 golang.google.cn,并先设置 export GOPROXY=https://goproxy.cn,direct。
curl -fsSL https://go.dev/dl/go1.27.2.linux-amd64.tar.gz | sudo tar -C /usr/local -xz
export PATH=$PATH:/usr/local/go/bin
go install tailscale.com/cmd/derper@latest
sudo install -m 755 ~/go/bin/derper /usr/local/bin/derper
Tailscale 客户端升级后偶尔需要新版 derper,每隔几个月重新运行后两条命令并重启服务即可。
2. 用 systemd 运行
sudo tee /etc/systemd/system/derper.service > /dev/null <<'EOF'
[Unit]
Description=Tailscale DERP server
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/derper -hostname derp.example.com -a :443 -http-port 80 -stun-port 3478 -certdir /var/lib/derper/certs
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now derper
默认的证书模式会在第一次 HTTPS 访问时向 Let's Encrypt 申请证书并自动续期,所以域名解析要已生效,80、443 端口要能从公网访问。验证:
curl https://derp.example.com
返回的 HTML 里有 This is a Tailscale DERP server 即部署成功。出错时用 journalctl -u derper -f 查看日志。
3. 只允许自己的设备使用
默认任何人知道你的域名,都能把它加进自己的 DERP 列表,占用你的带宽。按接入方式二选一,在 ExecStart 那一行末尾追加参数:
- Tailscale 官方:先在这台服务器上安装 Tailscale 并登录同一个账号(
curl -fsSL https://tailscale.com/install.sh | sh,再sudo tailscale up),然后追加-verify-clients。derper 通过本机的 tailscaled 判断连接者是否属于你的 tailnet。在管理后台为这台机器关闭密钥过期(Disable key expiry),否则过期后所有中继请求都会被拒绝。 - Headscale:追加
-verify-client-url=https://hs.example.com/verify -verify-client-url-fail-open=false。有设备接入时 derper 询问 Headscale,只放行在 Headscale 登记过的设备;第二个参数让 Headscale 暂时不可达时拒绝连接,默认是放行。
sudo systemctl daemon-reload && sudo systemctl restart derper
接入 Tailscale 官方服务
打开 Tailscale 管理后台的 Access controls,切换到 JSON 编辑器,在策略文件最外层加入 derpMap:
{
// 原有的 grants、acls、tagOwners 等保持不变
"derpMap": {
"Regions": {
"900": {
"RegionID": 900,
"RegionCode": "myderp",
"RegionName": "My DERP",
"Nodes": [
{
"Name": "900a",
"RegionID": 900,
"HostName": "derp.example.com",
"IPv4": "203.0.113.10"
}
]
}
}
}
}
- 区域 ID 900–999 留给自建节点。每个区域放一台服务器,有多台就分别建 900、901……
IPv4可选,建议填写,DNS 出问题时设备仍能连上中继。- 端口不是 443 时在节点里加
"DERPPort": 33445,STUN 端口不是 3478 时加"STUNPort"。 - 官方节点会保留作为后备,设备自动选延迟最低的区域。只想用自己的节点时,在
derpMap中加"OmitDefaultRegions": true;但这台服务器一旦故障,无法直连的设备就会断开。
保存后新的 DERP 列表会立即下发到所有设备,不用重启客户端。
Headscale:开启内置 DERP
Headscale 自带 DERP 服务器,使用 server_url 的域名、端口和证书,不用另装 derper。编辑 /etc/headscale/config.yaml:
derp:
server:
enabled: true
region_id: 999
region_code: "headscale"
region_name: "Headscale Embedded DERP"
verify_clients: true # 只允许本 Headscale 的设备使用
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: 203.0.113.10 # VPS 的公网 IP
urls:
- https://controlplane.tailscale.com/derpmap/default # 保留官方节点作后备
paths: []
auto_update_enabled: true
update_frequency: 3h
sudo ufw allow 3478/udp
sudo systemctl restart headscale
server_url必须是 https。用 Caddy 反向代理时不用改;用 nginx 时要转发Upgrade和Connection请求头。- VPS 访问不了
controlplane.tailscale.com时,Headscale 会因下载不到官方 DERP 列表而启动失败,此时把urls改成[],只用自建节点。 - 内置 DERP 没有做吞吐优化,自用足够;中继流量大时改用独立的 derper。
Headscale:使用独立 derper
中继放在另一台离你更近的服务器上时,先按部署 derper装好(客户端验证用 -verify-client-url),再在 Headscale 所在的 VPS 上新建 /etc/headscale/derp.yaml,键名全部小写:
regions:
900:
regionid: 900
regioncode: myderp
regionname: My DERP
nodes:
- name: 900a
regionid: 900
hostname: derp.example.com
ipv4: 203.0.113.10
stunport: 3478
derpport: 443
在 config.yaml 中引用它:
derp:
server:
enabled: false
urls:
- https://controlplane.tailscale.com/derpmap/default
paths:
- /etc/headscale/derp.yaml
auto_update_enabled: true
update_frequency: 3h
运行 sudo systemctl restart headscale 后生效。内置 DERP 和独立 derper 也可以同时用:保持 server.enabled: true,再在 paths 中加上 derp.yaml。
验证
在任意一台已加入网络的电脑或服务器上运行:
tailscale netcheck
DERP 延迟列表里应出现你的节点,并且延迟最低,输出类似:
* Nearest DERP: My DERP
* DERP latency:
- myderp: 12.3ms (My DERP)
- hkg: 48.9ms (Hong Kong)
- tok: 71.4ms (Tokyo)
Headscale 内置 DERP 的区域代码是 headscale。还可以检查连通性,并看到对端的实际路径:
tailscale debug derp myderp # 检查与该区域的 TLS 和 STUN
tailscale ping 100.64.0.2 # 对端的 Tailscale IP
tailscale ping 先显示 via DERP(myderp),打洞成功后变成 via 198.51.100.7:41641 直连。Roamtty 里不用做任何设置,照常用服务器的 100.x.y.z 地址连接。
国内服务器
中国大陆的云服务器上,未备案域名访问 80 / 443 会被拦截,Let's Encrypt 的验证也随之失败。做法是换一个高位端口(下例 33445),并用 DNS 验证申请证书。
1. 用 DNS 验证申请证书
以 DNS 托管在 Cloudflare 为例,用 acme.sh 申请(API 令牌需要该域名的 DNS 编辑权限;其他 DNS 服务商见 acme.sh 文档),在 root 下运行:
curl https://get.acme.sh | sh -s email=you@example.com
export CF_Token=<Cloudflare API 令牌>
~/.acme.sh/acme.sh --issue --server letsencrypt --dns dns_cf -d derp.example.com
mkdir -p /etc/derper/certs
~/.acme.sh/acme.sh --install-cert -d derp.example.com \
--key-file /etc/derper/certs/derp.example.com.key \
--fullchain-file /etc/derper/certs/derp.example.com.crt \
--reloadcmd "systemctl restart derper"
证书文件名必须是 <域名>.crt 和 <域名>.key。acme.sh 会自动续期,并在续期后重启 derper 加载新证书。
2. 改用高位端口
把 derper.service 的 ExecStart 改为下面这行,关闭 80 端口,从目录读取证书;安全组放行 TCP 33445 和 UDP 3478:
ExecStart=/usr/local/bin/derper -hostname derp.example.com -a :33445 -http-port -1 -stun-port 3478 -certmode manual -certdir /etc/derper/certs
DERP 列表里同步改端口:Tailscale 策略文件的节点加 "DERPPort": 33445,Headscale 的 derp.yaml 写 derpport: 33445。
没有域名:直接用 IP
-hostname 填公网 IP 并使用 -certmode manual 时,derper 会自动生成自签名证书,并在日志里打印一段带 CertName 的节点配置:
ExecStart=/usr/local/bin/derper -hostname 203.0.113.10 -a :33445 -http-port -1 -stun-port 3478 -certmode manual -certdir /etc/derper/certs
sudo journalctl -u derper | grep CertName
把日志中的 "CertName": "sha256-raw:…" 加到节点配置里(derp.yaml 中写作 certname),HostName 填 IP。客户端按证书指纹校验,不需要域名和 CA 证书,但所有设备的 Tailscale 都要是 1.82 或更新的版本。
常见问题
| 现象 | 处理 |
|---|---|
netcheck 里没有自己的节点 | Tailscale:确认策略文件已保存。Headscale:确认已重启,运行 tailscale debug derp-map 查看是否有该区域,derp.yaml 键名是否小写 |
| 有节点但没有延迟数值 | DERP 端口不通或证书无效:在另一台机器上运行 curl -v https://derp.example.com(自定义端口加上 :33445),检查安全组 |
| Let's Encrypt 证书申请失败 | 域名未解析到本机、80 / 443 不通,或开了 Cloudflare 代理(橙色云);国内服务器见上一节 |
加了 -verify-clients 后都连不上 | 这台服务器上的 Tailscale 未登录、已过期或不在同一个 tailnet,运行 tailscale status 检查 |
| Headscale 启动失败,日志提到 DERPMap | VPS 访问不了 controlplane.tailscale.com,把 urls 设为 [] |
| 走自建中继后延迟仍高 | 中继延迟等于手机到中继再到服务器的往返,选两边都近的机房;给服务器放行 UDP 41641 有助于直连 |
Tailscale 与 Headscale 的基本配置见使用文档的从外网连接一节。