Docker + acme.sh 搭建 Tailscale 自建 DERP 中继节点完整记录

1. 方案架构

为了实现证书自动申请、续期与 DERP 服务无缝衔接,采用 Docker Compose 进行单机编排。acme-sh 容器负责通过 Cloudflare DNS 验证申请 Let’s Encrypt 证书并导出为 DERP 所需的格式;derper 容器挂载相同的证书目录并运行中继服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Cloudflare DNS-01 验证]


┌─────────── derp-acme (容器) ───────────┐
│ 使用 CF_Token 申请证书,并将其安装转换为 │
│ derp.example.com.crt / .key │
└───────────────────┬────────────────────┘
│ (共享挂载 ./certs)

┌────────── tailscale-derp (容器) ────────┐
│ 监听 9443(TCP) 与 13478(UDP) │
│ 自动加载证书并提供中继服务 │
└────────────────────────────────────────┘


2. 部署步骤

新建工作目录并配置 .env 文件

/opt/derp(或自定义目录)下新建 .env 文件,写入环境变量。

注意:Cloudflare API Token 必须具备 Zone.DNS:Edit 权限,否则 acme.sh 无法在域名下添加用于验证的 TXT 记录。

1
2
3
4
5
6
7
8
# 域名与 Token
DERP_DOMAIN=derp.example.com
CF_TOKEN=你的_Cloudflare_DNS_编辑权限_Token

# 自定义服务端口
DERP_TCP_PORT=9443
DERP_UDP_PORT=13478

编写 docker-compose.yml

配置两个服务。acme-sh 在首次启动时若检测到无证书文件,则触发申请并使用 --install-cert 转换导出,随后运行 crond 用于每日自动续期检测。

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
30
31
32
33
34
35
36
37
38
services:
acme-sh:
image: neilpang/acme.sh:latest
container_name: derp-acme
restart: always
environment:
- CF_Token=${CF_TOKEN}
volumes:
- ./certs:/acme.sh
entrypoint: >
sh -c "
if [ ! -f /acme.sh/${DERP_DOMAIN}_ecc/${DERP_DOMAIN}.crt ]; then
acme.sh --issue --dns dns_cf -d ${DERP_DOMAIN} --server letsencrypt;
mkdir -p /acme.sh/${DERP_DOMAIN}_ecc;
acme.sh --install-cert -d ${DERP_DOMAIN} --cert-file /acme.sh/${DERP_DOMAIN}_ecc/${DERP_DOMAIN}.crt --key-file /acme.sh/${DERP_DOMAIN}_ecc/${DERP_DOMAIN}.key;
fi;
exec crond -f"

derper:
image: fredliang/derper:latest
container_name: tailscale-derp
restart: always
ports:
- "${DERP_TCP_PORT}:${DERP_TCP_PORT}"
- "${DERP_UDP_PORT}:${DERP_UDP_PORT}/udp"
environment:
- DERP_DOMAIN=${DERP_DOMAIN}
- DERP_CERT_MODE=manual
- DERP_CERT_DIR=/app/certs/${DERP_DOMAIN}_ecc
- DERP_ADDR=:${DERP_TCP_PORT}
- DERP_HTTP_PORT=-1
- DERP_STUN_PORT=${DERP_UDP_PORT}
- DERP_VERIFY_CLIENTS=false
volumes:
- ./certs:/app/certs
depends_on:
- acme-sh

启动服务

清理历史缓存目录并启动容器:

1
2
docker compose down && rm -rf ./certs/* && docker compose up -d

确认 derp-acme 输出证书安装成功日志:

1
2
3
derp-acme  | Your cert is in: /acme.sh/derp.example.com_ecc/derp.example.com.cer
derp-acme | Installing cert to: /acme.sh/derp.example.com_ecc/derp.example.com.crt

确认 tailscale-derp 成功加载证书并开始监听:

1
2
tailscale-derp  | derper: serving on :9443 with TLS


3. Tailscale 控制台 ACL 配置

  1. 登录 Tailscale 管理后台,进入 Access Controls 页面。
  2. 将编辑器切换至 JSON editor
  3. 在根对象内追加 derpMap 配置,指定自定义 Region ID(如 901)及对应端口:
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
{
"acls": [
{ "action": "accept", "src": ["*"], "dst": ["*:*"] }
],
"derpMap": {
"OmitDefaultRegions": false,
"Regions": {
"901": {
"RegionID": 901,
"RegionCode": "hk-custom",
"RegionName": "Hong Kong Custom DERP",
"Nodes": [
{
"Name": "hk-01",
"RegionID": 901,
"HostName": "derp.example.com",
"DERPPort": 9443,
"STUNPort": 13478,
"InsecureForTests": false
}
]
}
}
}
}

保存配置,等待节点策略同步。


4. 状态验证与故障排查

客户端网络检查

在本地设备终端运行:

1
2
tailscale netcheck

控制台输出回显确认 hk-custom 节点已被识别,且延迟正常:

1
2
3
4
5
6
7
8
Report:
* UDP: true
* IPv4: yes
* Nearest DERP: Hong Kong Custom DERP
* DERP latency:
- hk-custom: 60.1ms (Hong Kong Custom DERP)
- tok: 230.7ms (Tokyo)

故障记录:远端节点单向失联

现象
本地测试 tailscale ping <对端IP> 依然绕道旧节点(如 tok),且响应时间过长。
运行 tailscale status 查看对端状态:

1
2
100.123.186.96  earzer  linux  active; relay "tok", tx 7644 rx 0

本地有流量发送(tx),但未收到任何对端回包(rx 0),表明远端 Linux 设备未刷新最新的 DERP 路由图,仍向旧中继节点发送握手包。

解决方法
登录该远端 Linux 设备,执行命令强制重置并刷新本地的 Tailscale 状态与中继映射缓存:

1
2
3
sudo tailscale down
sudo tailscale up --reset

重置后,两端节点均能正常通过自建的 hk-custom 节点进行双向中继通信。

引用

1.