Tailscale 自建 DERP 服务器(Ubuntu)
校对日期:2026-09-04。本文基于 Tailscale 当前官方文档、v1.102.3 稳定版源码和最新策略字段整理。
DERP 是 Tailscale 的加密中继服务。节点无法建立直连或 Peer Relay 时,才会通过 DERP 转发已加密的 WireGuard 数据。Tailscale 目前仍将自建 DERP 标记为 Alpha;如果问题是频繁无法直连,仍应优先排查 NAT 和防火墙。
方案选择
| 方案 | 对外地址 | 证书 | 续期 | 适用场景 |
|---|---|---|---|---|
| A:内置 Let's Encrypt | 域名 | derper 自动申请 | 自动 | 默认推荐,最简单 |
| B:外部证书 | 域名 | 外部 ACME、公信或私有 CA | 外部程序负责 | 已有统一证书管理,或需要 DNS-01 |
| C:纯 IP 公信证书 | 公网 IP | Let's Encrypt 短期 IP 证书 | 自动 | 无域名时的纯 IP 优选方案 |
| D:纯 IP 自签 | 公网 IP | derper 自动生成,CertName 指纹锁定 | 手动 | 无法使用 ACME IP 证书时的备选 |
方案 D 同时属于“自签”和“纯 IP”。derper 只会在 --hostname 是 IP 且相应证书文件不存在时自动生成自签证书;域名场景没有必要使用自签证书。
统一约定
文中使用以下示例值,部署时必须全部替换:
| 配置 | 示例值 | 说明 |
|---|---|---|
| DERP 域名 | derp.example.com | 方案 A/B 使用 |
| 公网 IPv4 | 203.0.113.10 | 文档专用示例地址,不可直接使用 |
| 公网 IPv6 | 2001:db8::10 | 可选;没有可用 IPv6 时显式填 none |
| 自定义区域 ID | 900 | Tailscale 为用户保留 900–999 |
| DERP HTTPS 端口 | 443/tcp | DERPPort 的默认值 |
| STUN 端口 | 3478/udp | STUNPort 的默认值 |
| 持久化目录 | /var/lib/derper | 存放 derper 私钥和证书 |
1. 准备网络
- VPS 应有固定、公网可路由的 IP,且客户端能直接访问。
- 方案 A/B 需要创建
derp.example.com的 A 记录;只在 IPv6 确实可用时创建 AAAA 记录。 - 不要在 DERP 前放 CDN、HTTP 反向代理或全局负载均衡器。使用 Cloudflare DNS 时,应设为“仅 DNS”。
- 在云平台安全组和主机防火墙中放行入站 TCP 443、UDP 3478 以及入站/出站 ICMP。方案 A/C 还必须放行 TCP 80。
- 不要对 UDP 3478 做限速,也不要使用会吞掉 TCP RST 的防火墙策略。
如果主机已使用 UFW:
sudo ufw allow 443/tcp
sudo ufw allow 3478/udp
# 仅方案 A/C 必须;方案 B/D 可选
sudo ufw allow 80/tcp
sudo ufw statusTCP 80 对方案 A/C 的 ACME HTTP-01 验证是必需的,不能关闭。方案 B/D 不依赖 HTTP-01,可用 --http-port=-1 禁用 HTTP 监听;代价是该节点不再提供 /generate_204 和捕获门户检测。
2. 安装 Go 和 derper
derper 没有官方二进制包,需用最新 Go 从源码构建。Ubuntu 可使用 Snap:
sudo apt update
sudo apt install -y snapd
sudo snap install go --classic
go version在 Go Wiki 列出的 Ubuntu 18.04、20.04、22.04 和 24.04(amd64、arm64 或 armhf)上,也可选用 longsleep/golang-backports PPA:
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:longsleep/golang-backports
sudo apt update
sudo apt install -y golang-go
go version该 PPA 是社区维护包,不由 Go 项目创建或提供支持,发布节奏也可能落后。如果编译 derper 时提示 Go 版本过低,应改用 Snap 或 Go 官方安装包,不要强行使用旧编译器。本文不写死易过期的 Go 版本号。
构建并安装 derper:
go install tailscale.com/cmd/derper@latest
sudo install -m 0755 "$(go env GOPATH)/bin/derper" /usr/local/bin/derper
/usr/local/bin/derper --version纯 IP 公信证书方案还应确认当前二进制已支持 --acme-ip-certs:
/usr/local/bin/derper --help 2>&1 | grep -- '--acme-ip-certs'如果下载 Go 模块超时,再根据所在网络配置可信的 GOPROXY,不要在文档中写死第三方代理。
3. 创建运行用户
sudo useradd --system --home-dir /var/lib/derper --shell /usr/sbin/nologin derper
sudo install -d -o derper -g derper -m 0750 /var/lib/derper /var/lib/derper/certs如果 derper 用户已存在,只需执行第二条命令。
4. 准备证书
四种方案只选一种。
方案 A:域名 + 内置 Let's Encrypt
无需手工准备证书。derper 通过 TCP 80 完成 HTTP-01 验证,并把证书缓存到 /var/lib/derper/certs。
确保:
derp.example.com已解析到该 VPS。- TCP 80/443 可从公网直接访问。
- 域名没有经过 CDN 代理。
方案 B:域名 + 外部证书
外部证书必须满足:
- SAN 包含
derp.example.com。 - 由客户端系统信任的 CA 签发。私有 CA 需要在所有客户端安装根证书。
.crt中包含服务器证书和中间证书,即 full chain,不能只复制 leaf certificate。- 私钥与证书匹配。
将外部证书复制为 derper 要求的固定文件名:
sudo install -o root -g derper -m 0644 \
/path/to/fullchain.pem \
/var/lib/derper/certs/derp.example.com.crt
sudo install -o root -g derper -m 0640 \
/path/to/privkey.pem \
/var/lib/derper/certs/derp.example.com.key--certmode=manual 会读取 <certdir>/<hostname>.crt 和 <certdir>/<hostname>.key。证书续期后必须重新复制文件并重启 derper;只有 derper 内置 Let's Encrypt 模式会自动申请和轮换证书。
Cloudflare Origin CA 证书不适用于这个直连方案:它通常不被终端系统直接信任,而 DERP 也不应放在 Cloudflare HTTP 代理后。
方案 C:纯 IP + Let's Encrypt IP 证书
新版 derper 支持 --acme-ip-certs,可按需为 IPv4 或 IPv6 申请约 6 天有效期的 Let's Encrypt IP 证书并自动续期。该方案不需要 DNS。
要求:
- 公网 IP 直接配置在 derper 主机网卡上。
- Let's Encrypt 能直接访问该 IP 的 TCP 80 完成 HTTP-01。
- 不经过 NAT、反向代理或负载均衡。
截至本文校对日期,derper 在“网卡只有私网 IP、公网 IP 通过 NAT 映射”的环境中,可能会错误地为私网 IP 申请证书。这种 VPS 应改用方案 A、B 或 D,进展见 Tailscale #20660。
方案 D:纯 IP + 自签证书
无需手工运行 OpenSSL。在 --hostname 填公网 IP、--certmode=manual 且相应证书文件不存在时,derper 会自动:
- 生成 ECDSA P-256 私钥。
- 生成 SAN 包含该 IP、有效期 1 年的自签证书。
- 在日志中输出
sha256-raw:<fingerprint>形式的CertName。
客户端会同时校验 IP SAN、证书有效期和 SHA-256 指纹,不需要 InsecureForTests。证书指纹锁定在 Tailscale v1.82 引入,但早期实现曾有问题;服务端和客户端都应使用当前稳定版。
5. 配置 systemd
5.1 选择证书模式
创建配置文件:
sudoedit /etc/default/derper根据所选方案,只写入下面四组中的一组。
方案 A
DERPER_HOSTNAME=derp.example.com
DERPER_CERTMODE=letsencrypt
DERPER_HTTP_PORT=80
DERPER_EXTRA_ARGS=方案 B
DERPER_HOSTNAME=derp.example.com
DERPER_CERTMODE=manual
DERPER_HTTP_PORT=-1
DERPER_EXTRA_ARGS=方案 C
DERPER_HOSTNAME=203.0.113.10
DERPER_CERTMODE=letsencrypt
DERPER_HTTP_PORT=80
DERPER_EXTRA_ARGS=--acme-ip-certs方案 D
DERPER_HOSTNAME=203.0.113.10
DERPER_CERTMODE=manual
DERPER_HTTP_PORT=-1
DERPER_EXTRA_ARGS=5.2 创建统一的 systemd 服务
sudoedit /etc/systemd/system/derper.service写入:
[Unit]
Description=Tailscale DERP relay
Documentation=https://tailscale.com/docs/reference/derp-servers/custom-derp-servers
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=derper
Group=derper
EnvironmentFile=/etc/default/derper
ExecStart=/usr/local/bin/derper \
--hostname=${DERPER_HOSTNAME} \
--a=:443 \
--http-port=${DERPER_HTTP_PORT} \
--stun=true \
--stun-port=3478 \
--certmode=${DERPER_CERTMODE} \
$DERPER_EXTRA_ARGS \
--certdir=/var/lib/derper/certs \
--c=/var/lib/derper/derper.key
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/derper
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target--hostname 是 TLS 证书的域名或 IP,不是监听地址。--a=:443 表示在所有本机地址的 TCP 443 上监听;--http-port=-1 则会完全禁用额外的 HTTP 监听。
启用服务:
sudo systemctl daemon-reload
sudo systemctl enable --now derper
sudo systemctl status derper --no-pager
sudo journalctl -u derper -n 100 --no-pager首次启动失败时,优先检查 /etc/default/derper、DNS、端口和证书文件名,不要通过 InsecureForTests 规避 TLS 错误。
5.3 获取自签证书指纹
仅方案 D 需要此步。启动成功后,可直接从证书计算与 derper 日志相同的 CertName:
sudo openssl x509 \
-in /var/lib/derper/certs/203.0.113.10.crt \
-outform DER |
sha256sum |
awk '{print "sha256-raw:" $1}'保存完整输出,稍后填入 derpMap 的 CertName。也可从日志中查找 derper 自动输出的指纹。
6. 配置 Tailnet 策略
Tailscale 策略文件使用 HuJSON,支持注释和尾随逗号。将以下字段合并到现有顶层对象中,不要覆盖已有的 grants、ACL 或其他策略。
6.1 域名方案完整示例
方案 A/B 使用以下配置。注释中同时列出了当前 DERPMap、DERPRegion 和 DERPNode 的其他字段:
// 国内运营商网络、多层 NAT 或对固定 UDP 41641 不友好时建议开启。
// 它改变的是客户端 WireGuard UDP 端口,不是 DERP 端口。
"randomizeClientPort": true,
"derpMap": {
// HomeParams 是首选 DERP 选择参数。普通部署建议省略。
// RegionScore 是区域 ID 到延迟系数的映射:
// 0 < score < 1 会提高该区域优先级,score > 1 会降低优先级;
// 未写时按 1.0 处理,小于等于 0 的值会被忽略。
// 它只影响首选区域计算,不会篡改 netcheck 显示的实测 RTT。
// "HomeParams": {
// "RegionScore": {
// "900": 0.5,
// },
// },
// false:保留 Tailscale 官方 DERP 作为备选;
// true:只使用本 derpMap 中的自定义区域。一般不建议设为 true。
"OmitDefaultRegions": false,
// DERP 区域表,key 是字符串形式的 RegionID。
// 也可将已有官方区域的值设为 null,单独禁用该区域。
"Regions": {
"900": {
// 区域唯一 ID,必须与外层 key 和节点 RegionID 一致。
// 900–999 专供用户自建 DERP。
"RegionID": 900,
// 区域短代码,通常使用城市、机场或自定义缩写。
"RegionCode": "myderp",
// 人类可读的英文区域名称。
"RegionName": "My DERP",
// 可选:区域所在城市的纬度和经度。
// "Latitude": 31.2304,
// "Longitude": 121.4737,
// Avoid 已废弃,不要再使用。需要相应行为时使用 NoMeasureNoHome。
// "Avoid": true,
// true 时不测量该区域,也不将它选为 home DERP;
// 仅在某个 peer 已声明它是 home DERP 时才允许连接。普通部署省略。
// "NoMeasureNoHome": true,
// 区域内节点按优先级排列。
// 自建部署按官方要求每个区域只放一个 DERP 节点;
// 需要高可用时,建立多个单节点区域。
"Nodes": [
{
// 全部区域范围内唯一的节点名,不是域名。
"Name": "900a",
// 节点所属区域,必须与父级 RegionID 一致。
"RegionID": 900,
// 必填的节点域名或 IP。域名场景用于 TLS SNI;
// 未指定 IPv4/IPv6 时,还用于 DNS 解析和 TCP 拨号。
"HostName": "derp.example.com",
// 可选的证书校验名。省略时使用 HostName。
// 普通公信证书无需填;自签证书使用 sha256-raw:<64位指纹>。
// "CertName": "cert.example.com",
// 可选的固定公网 IPv4。填写后优先直接拨号,避免 DNS 故障。
// 必须是公网可路由地址;填 "none" 可禁用 IPv4。
"IPv4": "203.0.113.10",
// 可选的固定公网 IPv6。省略时通过 HostName 查询 AAAA;填 "none" 禁用 IPv6。
"IPv6": "none",
// STUN UDP 端口。0 或省略表示 3478,-1 表示关闭该节点的 STUN。
"STUNPort": 3478,
// true 表示该节点只提供 STUN,不提供 DERP 中继。普通 DERP 节点省略。
// "STUNOnly": true,
// DERP HTTPS 端口。0 或省略表示 443。
"DERPPort": 443,
// 仅供单元测试:跳过 TLS 校验。用户绝对不应设置。
// "InsecureForTests": true,
// 仅供测试:覆盖 STUN 服务器 IP。用户不应设置。
// "STUNTestIP": "192.0.2.1",
// 表示该节点的 HTTP 80 可访问,供捕获门户检测使用。
// 仅当 DERPER_HTTP_PORT=80 时设为 true;禁用 HTTP 时必须删除或设为 false。
"CanPort80": true,
},
],
},
},
},方案 A 保留 CanPort80: true。方案 B 按本文默认的 DERPER_HTTP_PORT=-1 部署时,必须删除 CanPort80;如果希望保留捕获门户检测,可将 B 的端口改回 80 并放行 TCP 80。
6.2 纯 IP 公信证书的节点配置
方案 C 保留上面的 derpMap 外层结构,将 Nodes 中的节点替换为:
{
"Name": "900a",
"RegionID": 900,
"HostName": "203.0.113.10",
"IPv4": "203.0.113.10",
"IPv6": "none",
"STUNPort": 3478,
"DERPPort": 443,
"CanPort80": true,
}Let's Encrypt IP 证书由系统公信根验证,因此不要填 CertName。
6.3 纯 IP 自签证书的节点配置
方案 D 的节点配置为:
{
"Name": "900a",
"RegionID": 900,
"HostName": "203.0.113.10",
"CertName": "sha256-raw:REPLACE_WITH_THE_64_HEX_FINGERPRINT",
"IPv4": "203.0.113.10",
"IPv6": "none",
"STUNPort": 3478,
"DERPPort": 443,
}必须将 CertName 替换为第 5.3 节实际计算出的完整值。证书更换后指纹也会改变,必须同步更新 Tailnet 策略。方案 D 默认禁用 HTTP,因此不填 CanPort80。
7. randomizeClientPort 的国内网络建议
在国内运营商网络、CGNAT 或多层 NAT 下,如果疑似光猫/防火墙对固定 UDP 41641 处理异常,可将它作为优先尝试的兼容性选项:
"randomizeClientPort": true,它使客户端用随机 UDP 端口承载 WireGuard 流量,而不是固定使用 41641。这是针对特定防火墙/NAT 的 workaround,不是通用加速开关;官方也建议只在排障确认需要时使用。它不会改变 DERP 的 TCP 443 或 UDP 3478,也不能保证一定打洞成功。
不要在以下场景全局开启:
- 客户端依赖
tailscaled --port或PORT=固定端口。 - 路由器为某个固定 UDP 端口做了静态端口映射。
- 现网已经可靠直连,且需要可预测的本地监听端口。
如果只想对特定设备开启,可用当前更精细的 nodeAttrs 方式,并删除全局 randomizeClientPort:
"nodeAttrs": [
{
// 替换为现有的 group、tag 或其他可用目标。
"target": ["group:cn-network"],
"attr": ["randomize-client-port"],
},
],8. 验证
8.1 服务和证书
方案 A/B 从 VPS 外部执行:
curl --fail --show-error --silent \
--output /dev/null --write-out '%{http_code}\n' \
https://derp.example.com/方案 C 使用公网 IP:
curl --fail --show-error --silent \
--output /dev/null --write-out '%{http_code}\n' \
https://203.0.113.10/应输出 200。方案 D 的证书不在系统信任链中,普通 curl 报“unknown authority”是预期行为;不要用 curl -k 作为配置正确的证明。
检查服务端监听和日志:
sudo ss -lntup
sudo journalctl -u derper -n 100 --no-pager8.2 Tailscale 客户端
在已登录该 tailnet 的最新版客户端执行:
tailscale version
tailscale netcheck
tailscale debug derp 900结果中应出现自定义区域,HTTPS、DERP 和 STUN 测试应成功。方案 D 应由 tailscale debug derp 900 按 CertName 完成指纹验证。
即使 DERP 可用,节点之间仍可能升级为直连;这是预期行为,不代表自建 DERP 失效。
9. 访问限制、续期与更新
限制其他 tailnet 使用
默认情况下,知道服务器地址的其他人也可以将它加入自己的 DERP Map。如果需要只允许本 tailnet,服务器必须同时运行已加入目标 tailnet 的 tailscaled,且 derper 与 tailscaled 应由同一 Tailscale 源码版本构建,然后才能在 DERPER_EXTRA_ARGS 中增加 --verify-clients。
方案 C 同时启用时,写为:
DERPER_EXTRA_ARGS="--acme-ip-certs --verify-clients"未满足本地 tailscaled 和版本一致性要求时,不要启用 --verify-clients。
证书续期
- 方案 A/C:derper 自动续期。
- 方案 B:由外部证书工具续期;续期后更新
.crt/.key并执行sudo systemctl restart derper。 - 方案 D:自签证书有效期 1 年,需手动轮换并同步更新
CertName指纹。不要直接覆盖老证书而不更新策略。
更新 derper
derper 应与 Tailscale 节点以相近频率更新:
go install tailscale.com/cmd/derper@latest
sudo systemctl stop derper
sudo install -m 0755 "$(go env GOPATH)/bin/derper" /usr/local/bin/derper
sudo systemctl start derper
sudo systemctl status derper --no-pagersystemd 的 Restart=on-failure 已负责异常退出后重启,无需再配置 cron 定时重启。如已启用 --verify-clients,更新时必须保持 derper 与 tailscaled 的源码版本一致。