Derper 搭建

目录

derper 是Tailscale/headscale 自建的中转节点,用来组网解决公共节点打洞成功率太低和延迟问题。

自建 DERP 节点

序

之前用一台国外 VPS 配合 sing-box 开过 DERP。和公共 DERP 相比,带宽能跑满,但因为线路和 NAT 环境问题,UDP 打洞一直没成功。

后来换成国内云主机,改用官方 derper 二进制重新部署。

如果机器上没有现成的 Nginx 和证书体系,优先用更简单的官方直跑方案:

  • -a :443
  • -http-port 80
  • -certmode letsencrypt

我最终采用的方案:Nginx 监听 443,后端 derper 监听 12443,证书由 Nginx 管理,derper 用 manual 模式读取证书。


最终架构

流量路径如下:

1
2
客户端 -> TCP 443 -> Nginx -> https://127.0.0.1:12443 -> derper
客户端 -> UDP 3478 ---------------------------------------> derper(STUN)

对应关系:

  • 443/tcp:DERP 客户端入口,走 Nginx
  • 3478/udp:STUN 打洞,直达 derper
  • 12443/tcp:后端 derper 监听端口,只给本机 Nginx 用,阿里云防火墙策略里面不对公网放行

--verify-clients 用来防止其他人白嫖 如果开启 --verify-clients:

  • DERP 机必须安装并接入 tailscaled
  • derper 会通过本机 tailscaled 判断客户端是不是自己 tailnet 的成员

端口规划

至少要确认这些端口:

  • 443/tcp:放行
  • 3478/udp:放行
  • 80/tcp:只在你需要 Nginx 做 ACME 或 HTTP 跳转时放行
  • 12443/tcp:不要在云安全组里放行

这样外部只能通过 Nginx 进来,但 STUN 还能正常工作。


前提条件

假设:

  • 系统是 Debian 12
  • 域名是 derp.example.com
  • 域名已经解析到 VPS 公网 IP
  • 证书由 Nginx 或其他现有流程维护

如果准备开启 --verify-clients,还需要:

  • 安装 tailscaled
  • 让这台 DERP 机加入正确的 tailnet
  • 最好给它打上 tag:derp

如果机器上启用了 Tailscale,且 apt update 超时,优先确认不是 DNS 或私网镜像源问题。长期建议:

1
sudo tailscale set --accept-dns=false --netfilter-mode=off

安装 Go 和 derper

验证 Go:

1
go version

如果没装 Go,自行安装到 /usr/local/go 后,补 PATH:

1
echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee /etc/profile.d/go.sh

创建运行目录:

1
2
sudo useradd --system --home /var/lib/derper --create-home --shell /usr/sbin/nologin derp
sudo install -d -o derp -g derp -m 0750 /var/lib/derper /var/lib/derper/certs

安装 derper:

1
sudo env PATH=$PATH GOBIN=/usr/local/bin /usr/local/go/bin/go install tailscale.com/cmd/derper@latest

如果国内网络太慢:

1
sudo env PATH=$PATH GOBIN=/usr/local/bin GOPROXY=https://goproxy.cn,direct /usr/local/go/bin/go install tailscale.com/cmd/derper@latest

验证:

1
/usr/local/bin/derper -version

安装并接入 Tailscale

如果要用 --verify-clients,DERP 机必须先入网:

1
2
3
4
curl -fsSL https://tailscale.com/install.sh | sh
sudo systemctl enable --now tailscaled
sudo tailscale up
sudo tailscale set --advertise-tags=tag:derp

检查状态:

1
2
3
tailscale version
systemctl status tailscaled
sudo tailscale status

证书准备

如果你已经有 Nginx 管理的证书,例如:

  • /etc/nginx/ssl/derp/cert.pem
  • /etc/nginx/ssl/derp/key.pem

那么 derper 使用 -certmode manual 时,不会直接认 cert.pem 和 key.pem 这种名字。它要求证书文件名必须和主机名一致:

  • derp.example.com.crt
  • derp.example.com.key

因此可以用软链接映射:

1
2
3
sudo mkdir -p /var/lib/derper/certs
sudo ln -sf /etc/nginx/ssl/derp/cert.pem /var/lib/derper/certs/derp.example.com.crt
sudo ln -sf /etc/nginx/ssl/derp/key.pem /var/lib/derper/certs/derp.example.com.key

这是这次排障里最关键的坑之一:

  • 开了 -certmode manual
  • 却没配 -certdir
  • 或者证书文件名不匹配主机名

结果就是 derper 直接启动失败,Nginx 对后端只能拿到 502 Bad Gateway。


derper.service

最终采用的 systemd 配置如下:

/etc/systemd/system/derper.service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[Unit]
Description=Tailscale DERP Server
Wants=network-online.target tailscaled.service
After=network-online.target tailscaled.service
Requires=tailscaled.service

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/var/lib/derper
NoNewPrivileges=true

ExecStart=/usr/local/bin/derper \
  -c /var/lib/derper/derper.key \
  -hostname derp.example.com \
  -a :12443 \
  -http-port -1 \
  -stun-port 3478 \
  -certmode manual \
  -certdir /var/lib/derper/certs \
  -stun \
  -verify-clients

Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

这里有几个关键点:

  • User=root:开启 --verify-clients 时,这样最省事,避免本地 tailscaled.sock 权限问题
  • -a :12443:不要写成 127.0.0.1:12443
  • -http-port -1:关闭 derper 自己的 HTTP 监听
  • -stun-port 3478:STUN 端口是纯数字,不要写成 :3478
  • -certmode manual + -certdir:让 derper 使用你已经准备好的证书
  • -verify-clients:限制只允许自己 tailnet 的客户端使用

启动:

1
2
3
4
sudo systemctl daemon-reload
sudo systemctl enable --now derper
sudo systemctl status derper
sudo journalctl -u derper -f

Nginx 反代配置

示例: /etc/nginx/sites-available/derp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server {
    listen 443 ssl http2;
    server_name derp.example.com;

    ssl_certificate /etc/nginx/ssl/derp/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/derp/key.pem;

    location / {
        proxy_pass https://127.0.0.1:12443;
        proxy_ssl_verify off;
        proxy_ssl_server_name on;
        proxy_ssl_name derp.example.com;

        proxy_buffering off;
        proxy_http_version 1.1;

        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 Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_connect_timeout 60s;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

启用并检查:

1
2
3
sudo ln -sf /etc/nginx/sites-available/derp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

这次排障确认了几个容易错的点:

  • proxy_pass 必须是 https://127.0.0.1:12443,不能写成 http://
  • proxy_ssl_server_name on 和 proxy_ssl_name derp.example.com 最好一起带上
  • proxy_buffering off 建议开启,DERP 这种长连接/流式转发场景更稳

tailnet policy 和 derpMap

示例:

这里的重点是:

  • DERPPort 写 443,因为客户端实际连的是 Nginx,不是后端 12443
  • STUNPort 还是 3478
  • 开启 --verify-clients 时,不要把 ACL 写得过死,至少先保证 tag:derp 能“看见”允许使用它的节点

ACL 写太严时,tailscale ping 和节点探测都出现异常。先跑通,再慢慢收紧规则更稳妥。

如果需要 ping,别忘了 ACL 里要考虑 icmp。


验证方法

服务状态

1
2
sudo systemctl status derper
sudo journalctl -u derper -n 100 --no-pager

端口监听

1
2
sudo ss -tlnp | grep 12443
sudo ss -ulnp | grep 3478

期望结果:

  • 12443/tcp 在监听
  • 3478/udp 在公网地址上监听,而不是只绑在 127.0.0.1

域名访问

1
curl -I https://derp.example.com

Tailscale 状态

1
2
3
tailscale netcheck
tailscale ping <peer>
tailscale status

如何判断:

  • 看到自定义 DERP 区域,说明 derpMap 生效
  • 看到 relay "sz" 之类信息,说明 DERP 可作为中转保底
  • 看到 direct,说明 UDP 打洞成功,已经不是纯 DERP 中转

这次排障里最关键的结论

  1. 502 Bad Gateway 不等于真的是 443 被拦了 很多时候只是 Nginx 能起来,但后端 derper 没起来。 先查:
1
2
3
sudo systemctl status derper
sudo journalctl -u derper -n 50 --no-pager
sudo ss -tlnp | grep derper

如果 12443 没监听,优先看 derper 启动参数和证书。

  1. manual 模式下,证书目录和文件名必须对 这次最开始的 502,根因之一就是:
  • derper 开了 -certmode manual
  • 但没找到正确的证书 要么缺 -certdir,要么证书不是按主机名命名。
  1. Nginx 反代 derper 时,上游必须走 HTTPS derper 在这套配置下是 TLS 服务。如果 Nginx 写成:
1
proxy_pass http://127.0.0.1:12443;

那就属于协议对不上,最后可能得到 400 或 502。

  1. -a 127.0.0.1:12443 会把 STUN 一起绑到回环地址 这是这次踩的另一个大坑。 如果把 -a 绑到 127.0.0.1,很多版本的 derper 会让 3478/udp 也只监听回环地址。结果就是:
  • 网页可能正常
  • DERP 中转可能正常
  • 但 direct 很难成功,因为外部客户端根本打不到 STUN 因此最终保留:
1
2
-a :12443
-stun-port 3478

然后通过云安全组不开放 12443/tcp 来补安全。

  1. derpMap 要指向 443,不是 12443 客户端连的是:
1
客户端 -> 443 -> Nginx -> 12443 -> derper

所以 DERPPort 必须写 443。

  1. --verify-clients 依赖本机 tailscaled 的可见范围 如果日志里出现:
1
client nodekey:... rejected: peer nodekey:... not authorized (not found in local tailscaled)

优先检查:

  • DERP 机是否已经加入正确的 tailnet
  • tag:derp 是否配置正确
  • ACL / grants 是否让 DERP 机能看到这些客户端 如果只是临时排障,也可以先去掉 -verify-clients,等网络和反代都跑通再加回来。
  1. 海外节点访问异常时,要分清是配置问题还是安全组/线路问题 如果外地或海外节点完全打不到你的自建 DERP: 先在远端做两类测试:
1
2
curl -I https://derp.example.com
nc -zvu 1.2.3.4 3478

然后回头检查:

  • 云安全组是否放行了 443/tcp
  • 云安全组是否放行了 3478/udp
  • 12443/tcp 是否没有对公网放行
  • 是否确实存在跨境线路抖动或区域性阻断

安全建议

这一套最终的安全边界是:

  • 公网开放 443/tcp
  • 公网开放 3478/udp
  • 公网不开放 12443/tcp
  • derper 开启 --verify-clients

这样即使 derper 进程监听了 :12443,外部也很难直接打到它。

如果你只是临时测试:

  • 可以先不启用 --verify-clients
  • 先把 Nginx -> derper、derpMap、STUN 跑通
  • 最后再加访问控制

最终判断标准

满足以下条件就算部署完成:

  • derper.service 是 active (running)
  • curl -I https://derp.example.com 正常返回
  • 3478/udp 对外可达
  • tailscale netcheck 能看到自定义 DERP 区域
  • 需要中转时能走到自建 DERP
  • 网络允许时能出现 direct
  • 未授权客户端会被 --verify-clients 拒绝

参考

官方文档

  1. DERP servers
  2. Device connectivity
  3. Group devices with tags
  4. Grants syntax
  5. Tailscale CLI reference

官方仓库