
起因
手机上通过某第三方客户端内嵌的 Tailscale 模块连回家里内网,延迟高得离谱——tailscale ping 动辄 300ms 起步,还会抖到 1.6 秒,速度慢到基本不可用。表面是"网络慢",深挖下去是一整条链路的问题。这篇记录完整的排查与解决过程,也算给自己留个底。
第一步:定位根因——中继落在境外
高延迟在 Tailscale 场景里几乎都指向同一个根因:流量走了 DERP 中继,而不是 P2P 直连。先在 NAS 上定位:
tailscale ping 100.x.x.x
# pong from node via DERP(hkg) in 381ms ... direct connection not establishedvia DERP(hkg)——中继落在境外节点。再看 netcheck:
Nearest DERP: Hong Kong
hkg: 145.6msNAS 到官方境外 DERP 就要 145ms,手机(蜂窝网络)到该节点又是一段,两段叠加加抖动,300ms+ 就是这么来的。P2P 直连没打通,全程依赖这个远距离中继。
结论:得把中继点从境外换到境内。
第二步:尝试 peer relay
Tailscale 的 peer relay(对等中继)走 UDP,延迟特性接近直连,比 DERP(TCP/TLS)更优,是"打洞失败但还没沦落到 DERP"的中间层。我在一台境内云服务器上部署它。
踩坑记录
一开始以为打了 tag:relay 标签就算部署好了,实际差得远:
tailscale debug prefs里 grep 到的tag:relay只是身份标签,不是 relay 服务本身;- peer relay 真正的启用是写入
RelayServerPort:
tailscale set --relay-server-port=41641启用后确认:
tailscale debug prefs | grep -i relay
# "RelayServerPort": 41641
ss -lunp | grep 41641
# tailscaled 在 41641/udp 上 LISTEN(v4+v6 双栈)ACL 里也补上了 grant:
"grants": [
{ "src": ["*"], "dst": ["tag:relay"],
"app": { "tailscale.com/cap/relay": [] } }
]云服务器安全组放行 41641/UDP(这台没装 ufw,系统层不拦,但云平台安全组是独立一层,必须在控制台放行)。
验证服务端本身没问题
关键的一步——从 NAS 直接 ping 这台 relay:
tailscale ping 100.x.x.x
# pong from relay-vps via <公网IP>:41641 in 17msdirect UDP 直连,17ms,走的正是 41641。 说明服务端、端口、安全组、端点宣告全部正常。再 ping 另一台海外机器(官方客户端)也是 direct。
但手机就是走不通
配置全齐了,手机的 tailscale ping 依然死守 via DERP(hkg)。做了对照实验后规律清晰:
| 链路 | 结果 | 性质 |
|---|---|---|
| NAS ↔ 境内 VPS | 17ms | direct ✅ |
| NAS ↔ 海外机器 | 186ms | direct ✅ |
| NAS ↔ 手机(第三方客户端) | 300ms+ | via DERP(hkg) ❌ |
所有官方 Tailscale 客户端之间都能 direct,唯独跟手机(第三方客户端内嵌模块)之间只能走 DERP。 问题不在服务端,在那个第三方客户端的内嵌 Tailscale 实现——它不支持 peer relay 协商。这个变量我控制不了。
第三步:自建 DERP(derper)——不挑客户端的兜底方案
peer relay 依赖对端支持,而对端是第三方客户端,这条路对我的实际场景走不通。转向 derper:它是 DERP 协议,所有 Tailscale 客户端天生都会用,包括那个内嵌模块,配好即用、必然生效。
而且实测这台境内 VPS 到我家公网 IP 只有 14ms、到 NAS 17ms(netcheck 显示"最近 DERP 在境外"是被国际出口策略绕路误导,真实 UDP 路径是境内直达的),位置其实极佳,正是理想的 derper 载体。
部署要点(derper 独立跑 8443,与 Web 服务的 443 共存)
443 端口被这台上的 Caddy 占用(跑网站),所以让 derper 跑 8443,两者井水不犯河水。
证书:让 Caddy 负责签发和续期,derper 用 manual 模式读现成证书。在 Caddyfile 加一个块为你的 DERP 子域名签证书。
这里踩了个低级坑:一开始把子域名的后缀写错了,Caddy 一直报 NXDOMAIN。改对后证书秒签。证书同步脚本(应对 Caddy 每 60 天续期):
#!/bin/bash
DOMAIN=derp.example.com # 替换成你自己的域名
SRC_CRT=$(find /var/lib/caddy -name "${DOMAIN}.crt" 2>/dev/null | head -n1)
SRC_KEY=$(find /var/lib/caddy -name "${DOMAIN}.key" 2>/dev/null | head -n1)
DST=/var/lib/derper
if ! cmp -s "$SRC_CRT" "$DST/${DOMAIN}.crt"; then
cp "$SRC_CRT" "$DST/${DOMAIN}.crt"
cp "$SRC_KEY" "$DST/${DOMAIN}.key"
systemctl restart derper 2>/dev/null || true
fi配 systemd timer 每天同步一次。
装 derper(这台没有 Go,得先装;境内网络用国内镜像下 Go、goproxy.cn 拉依赖):
wget https://mirrors.aliyun.com/golang/go1.25.5.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.25.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go env -w GOPROXY=https://goproxy.cn,direct
go install tailscale.com/cmd/derper@latest
cp ~/go/bin/derper /usr/local/bin/derperderper 的 systemd 服务:
[Service]
ExecStart=/usr/local/bin/derper \
-hostname derp.example.com \
-a :8443 \
-http-port -1 \
-certmode manual \
-certdir /var/lib/derper \
-stun -stun-port 3478 \
-verify-clients
Restart=always几个关键 flag:-http-port -1 禁掉 derper 自己的 80 监听避让 Caddy;-verify-clients 让它只给自己 tailnet 的节点中继、防被白嫖(靠本机已在 tailnet 里的 tailscaled 校验身份)。
安全组:放行 8443/TCP 和 3478/UDP。
ACL 的 derpMap(RegionID 必须 ≥900 避免和官方冲突):
"derpMap": {
"OmitDefaultRegions": false,
"Regions": {
"900": {
"RegionID": 900,
"RegionCode": "cn-self",
"RegionName": "Self Hosted",
"Nodes": [{
"Name": "900a",
"RegionID": 900,
"HostName": "derp.example.com",
"DERPPort": 8443,
"STUNPort": 3478
}]
}
}
}验证成功
服务端起来后 curl -vk https://derp.example.com:8443/ TLS 握手成功、返回标准的 "This is a Tailscale DERP server" 页面。NAS 上 netcheck:
- cn-self: 15.8ms (Self Hosted) ← 自建 DERP
- hkg: 158.1ms (Hong Kong) ← 官方境外,慢 10 倍NAS 侧最近的 DERP 变成了自建节点,15.8ms,把境外的 158ms 打得稀烂。
第四步:真正的最后一公里——换客户端
derper 建好了、NAS 侧也选中了自建节点,但手机的 tailscale ping 依然 via DERP(hkg)。进一步验证:
- 手机的
Relay字段仍钉在境外节点; - 但手机浏览器能打开 DERP 的 HTTP 页面——网络完全可达。
实锤了:那个第三方客户端内嵌的 Tailscale 模块既不支持 peer relay,也不加载自定义 derpMap。 这不是服务端能修的,是那个客户端实现的天花板。它消费不了我辛苦搭好的基础设施。
解法很简单:手机装官方 Tailscale App。 一换客户端,天翻地覆:
tailscale ping 100.x.x.x
# pong from iphone via peer-relay(192.168.x.x:40000:vni:xxxx) in 33ms- 设备从第三方客户端变成官方 App;
- 延迟从 300ms+ 直接砍到 25–41ms;
- 而且走的是 peer-relay——官方客户端正确支持了 peer relay,反过来 100% 印证了之前的判断:peer relay 服务端早就配对了,一直走不通就是那个第三方客户端的锅。
至此,peer relay 和 derper 两套建设成果同时兑现,没有一步是白做的。
复盘:证据链与教训
整条排查线的核心结论:服务端每一环都建对了、也验证了(自建 DERP 15.8ms、NAS↔VPS 17ms direct、derper TLS 正常、peer relay 端口通),唯一自始至终的短板是那个第三方客户端内嵌 Tailscale 模块的能力缺陷。
几条经验:
- 高延迟先看
tailscale ping的via字段——direct/via DERP/via peer-relay决定了后面所有动作。 netcheck的"最近 DERP"可能被国际出口策略误导,真实链路质量要用 ICMP/UDP 直连实测(这台 VPS 就是典型:netcheck 说境外最近,实测到家 14ms)。- peer relay 依赖两端客户端都支持,是它的脆弱点;DERP 不挑客户端,是它可靠的原因。追求低延迟用 peer relay,追求稳定用 derper,两者可以共存。
- 境内自建 DERP 是国内用 Tailscale 的关键优化——官方 DERP 最近的节点也在 150ms 级别,自建境内节点能压到十几毫秒。
- 客户端选型比服务端调优更决定成败。折腾了一整晚服务端,最后发现最有效的动作是"换回官方 App"。内嵌 Tailscale 的第三方客户端目前普遍不成熟,涉及 Tailscale 高级特性时,官方客户端才是正解。
安全性备注
自建 DERP 的主要滥用面是"别人白嫖你的带宽做中继",-verify-clients 已经堵死——它只为通过本机 tailscaled 验证身份的节点(即自己 tailnet 的成员)中继。前提是这台的 tailscaled 要保持在线,否则验证链会断。STUN(3478)是公开的但无害(放大系数≈1)。HTTP 说明页公开但不含敏感信息。
顺手做了个探活脚本,覆盖 derper 服务、tailscaled 在线、8443 可达、证书有效期四项,异常就消息推送告警,给"境内单点 DERP 一挂中继全回退境外"的风险兜底。
最终架构
手机(官方 Tailscale App)
├─ 家里 WiFi → peer-relay(内网直连接力)→ 30ms
└─ 外出蜂窝 → 自建 DERP(境内中继) → 十几~几十ms- 内网访问(SSH 到 NAS、内部服务):官方 Tailscale App,配成按需/仅内网生效以省电;
- 网络分流:交给专门的分流客户端,各司其职。
两套分工,各干各的。最初"手机连内网延迟高、速度慢"的问题,彻底解决。