# Tailscale 访问速度优化记录

> 场景:南京电脑 ↔ 新加坡腾讯云服务器(`vm-ubuntu22` / 100.93.191.79 / 公网 43.156.23.211),
> 目标是稳定直连并把服务器当 exit node 访问外网。
> 记录日期:2026-07-12

---

## 一、问题诊断

在南京电脑执行 `tailscale ping` 和 `tailscale netcheck`,发现:

- 连接在**直连(direct)**和**中转(DERP)**之间抖动。
- 直连正常时约 **93ms**(走 `43.156.23.211:47804`),可用。
- 掉到 DERP 时延迟高达 **1.9 秒**并伴随丢包,基本不可用。
- `netcheck` 显示官方 DERP 节点全被降速:最近节点竟是旧金山(174ms),
  新加坡官方节点(sin)高达 **255ms** —— 典型的 GFW 干扰特征。
- NAT 环境良好:`UDP: true`、`MappingVariesByDestIP: false`(非对称 NAT)、UPnP 正常。

**结论:**
1. 直连本身可用(~93ms),抖动时会掉到被干扰的官方 DERP,拖慢体验。
2. 解决方案:自建 DERP 替换官方节点(兜底提速) + 打通 exit node(访问外网)。

---

## 二、自建 DERP 中转节点

### 1. DNS 解析
在 `stargazer.org.cn` 域名后台添加 A 记录:

| 类型 | 主机记录 | 记录值 | TTL |
|------|---------|--------|-----|
| A | `derp` | `43.156.23.211` | 600 |

即 `derp.stargazer.org.cn` → 新加坡服务器。

### 2. 申请 SSL 证书(webroot 方式,零影响)

服务器上 Apache 占用 80 端口跑着主站,用 webroot 方式申请子域名证书,
全程不改 Apache 配置、不重载,网站访问不受影响。

```bash
# 测试 webroot 可达
mkdir -p /var/www/html/.well-known/acme-challenge
echo "test123" > /var/www/html/.well-known/acme-challenge/test
curl http://derp.stargazer.org.cn/.well-known/acme-challenge/test   # 返回 test123 即可

# 申请证书
sudo certbot certonly --webroot -w /var/www/html \
  -d derp.stargazer.org.cn \
  --non-interactive --agree-tos -m clawdbot@stargazer.org.cn

# 清理测试文件
rm -f /var/www/html/.well-known/acme-challenge/test
```

证书路径:`/etc/letsencrypt/live/derp.stargazer.org.cn/`,有效期至 2026-10-10。

### 3. 准备 derper 证书文件

derper 要求证书命名为 `域名.crt` / `域名.key`:

```bash
sudo mkdir -p /var/www/html/data/derp-certs
sudo cp /etc/letsencrypt/live/derp.stargazer.org.cn/fullchain.pem \
   /var/www/html/data/derp-certs/derp.stargazer.org.cn.crt
sudo cp /etc/letsencrypt/live/derp.stargazer.org.cn/privkey.pem \
   /var/www/html/data/derp-certs/derp.stargazer.org.cn.key
```

### 4. 启动 derper 容器

- 原镜像 `ghcr.io/yangchuansheng/derper` 已下架,改用 `fredliang/derper`。
- 用非 443 端口 **33445**(避开 Apache 的 80/443)。
- 加 `DERP_HTTP_PORT=-1` 禁用 80 端口 HTTP 跳转(否则和 Apache 冲突,容器反复重启)。

```bash
sudo docker run --restart always --name derper -d \
  --network host \
  -e DERP_ADDR=:33445 \
  -e DERP_HTTP_PORT=-1 \
  -e DERP_DOMAIN=derp.stargazer.org.cn \
  -e DERP_CERT_MODE=manual \
  -e DERP_CERT_DIR=/app/certs \
  -v /var/www/html/data/derp-certs:/app/certs \
  fredliang/derper:latest
```

验证:`curl -s https://derp.stargazer.org.cn:33445/` 返回 DERP 欢迎页即成功。

### 5. 腾讯云安全组放行(关键)

| 协议 | 端口 | 说明 |
|------|------|------|
| TCP | 33445 | DERP 主通道 |
| **UDP** | **3478** | STUN,`netcheck` 测延迟靠这个,协议务必选 UDP |

> 注意:`netcheck` 测 DERP 延迟依赖 STUN(UDP 3478),
> 只放 TCP 会导致节点出现但**延迟空白**。

### 6. Tailscale ACL 注册自建节点

在 `https://login.tailscale.com/admin/acls/file`(原始 JSON 编辑器,
不是可视化 Add rule 表单)最外层加入 `derpMap`,与 `grants`/`ssh` 同级:

```json
"derpMap": {
    "Regions": {
        "900": {
            "RegionID":   900,
            "RegionCode": "mysin",
            "RegionName": "Singapore Self",
            "Nodes": [
                {
                    "Name":     "1",
                    "RegionID": 900,
                    "HostName": "derp.stargazer.org.cn",
                    "DERPPort": 33445,
                },
            ],
        },
    },
},
```

> 完整 ACL 参考:`/var/www/html/share/tailscale-acl.json`

### 7. 证书自动续期钩子

Let's Encrypt 90 天到期,续期后自动同步证书给 derper 并重启:

```bash
# /etc/letsencrypt/renewal-hooks/deploy/derp.sh
#!/bin/bash
cp /etc/letsencrypt/live/derp.stargazer.org.cn/fullchain.pem /var/www/html/data/derp-certs/derp.stargazer.org.cn.crt
cp /etc/letsencrypt/live/derp.stargazer.org.cn/privkey.pem /var/www/html/data/derp-certs/derp.stargazer.org.cn.key
docker restart derper
```

```bash
chmod +x /etc/letsencrypt/renewal-hooks/deploy/derp.sh
```

### 8. ⚠️ 踩坑:certbot 自动续期在 systemd 系统上默认失效

排查发现本机自动续期实际**不工作**,证书到期后 derper 会因证书过期挂掉。两个原因:

- `certbot.timer` 被 **masked**(软链指向 /dev/null),且此 certbot 安装**未自带 timer 单元**。
- `/etc/cron.d/certbot` 的任务带条件 `test ... \! -d /run/systemd/system`,
  意为"仅在**非** systemd 系统才执行"。本机是 systemd,故该 cron **永不触发**。

**解决:**新建独立 cron,绕开该限制:

```bash
# /etc/cron.d/certbot-derp
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 3,15 * * * root perl -e 'sleep int(rand(3600))' && certbot -q renew
```

每天 3、15 点各试一次(剩 30 天内才实际续),续期成功自动触发上面的
deploy hook 同步证书 + 重启 derper。cron 服务开机自启,重启后照常工作。

### 效果
南京电脑 `tailscale netcheck` 中 `mysin` 节点延迟 **95.5ms**
(官方 sin 节点 255ms → 自建 95.5ms,快 2.5 倍)。

---

## 三、Exit Node(访问外网)

### 1. 容器网络模式
检查发现 `tailscaled` 容器**已是 host 模式**,且已带 `--advertise-exit-node`,
数据卷 `/var/lib` 已挂载(认证持久化),无需重建。

```bash
sudo docker inspect tailscaled --format 'NetworkMode: {{.HostConfig.NetworkMode}}'
```

### 2. 服务器开启 IP 转发(已持久化)

运行时 IPv4 已开但未持久化,IPv6 未开。写入配置文件持久化:

```bash
sudo tee /etc/sysctl.d/99-tailscale.conf >/dev/null <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
```

### 3. 后台批准 exit node(手动)
`https://login.tailscale.com/admin/machines` → `vm-ubuntu22` →
`...` → Edit route settings → 勾选 **Use as exit node** → Save。

### 4. 南京电脑启用出口
```bash
tailscale up --exit-node=100.93.191.79
```
或 Windows 托盘图标 → Exit nodes → 选 `vm-ubuntu22`。

### 5. 验证(已通过 ✅)

操作顺序:
1. 后台批准 exit node(见上 3)
2. 南京电脑启用出口(见上 4)
3. 南京电脑访问 `https://myip.ipip.net` 或 `https://ip.sb`,
   显示**新加坡 IP** 即表示外网流量成功走服务器出口。

**结果:南京电脑访问外网已成功走新加坡出口,DNS 无污染,验证通过。**

### 排错:连上但网页打不开
多为 NAT 转发(masquerade)未生效。host 模式下 tailscale 通常自动配,
若异常手动补:
```bash
# eth0 换成实际公网网卡(ip route | grep default 查看)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
```

---

## 四、进阶优化

### 1. BBR 拥塞控制(已开启)

> 误区澄清:BBR 该不该开取决于**链路丢包率**,不是流量大小。
> 中国↔新加坡链路丢包是常态,只要有丢包,单条 TCP 流用 BBR 就比默认 cubic
> 明显更顺(网页加载、视频缓冲少卡顿),与流量大小无关,且零成本可逆。

内核默认只有 reno/cubic,需先加载 bbr 模块。已持久化配置:

```bash
# 加载模块 + 开机自动加载
sudo modprobe tcp_bbr
echo "tcp_bbr" | sudo tee /etc/modules-load.d/bbr.conf

# 追加到 /etc/sysctl.d/99-tailscale.conf
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
```

确认:`sysctl net.ipv4.tcp_congestion_control` 显示 `bbr` 即成功。

### 2. DNS 防污染(已配置 ✅)

走 exit node 时若 DNS 仍被污染/走国内解析,会出现"IP 是新加坡的但域名解析错误",
表现为慢、打不开。在 `https://login.tailscale.com/admin/dns`:

- 添加 Global nameserver:`1.1.1.1` 或 `8.8.8.8`
- 开启 **Override local DNS**

让域名解析也走干净通道。此项对体验的影响往往比任何吞吐优化都大。

### 3. MTU 调优(部分网站打不开时才需要)

很多"网站打不开/加载卡"并非速度问题,而是 **MTU 过大导致分片丢包**。
WireGuard 隧道叠加中新链路,默认 MTU 有时偏大。

- 启用 exit node 后若发现部分网站加载卡/打不开,在**南京电脑(客户端)**
  降低 Tailscale 网卡 MTU 到 **1280** 试试。
- 不卡就不用动。MTU 真正起作用的是客户端那端。

### 4. 保持直连,避免掉 DERP

直连 ~93ms,掉到 DERP(即使自建也有建连开销)。让直连别断:
服务器固定监听端口(host 模式加 `--port=41641`,并放行 UDP 41641),
有持续流量时直连一般不掉。

---

## 五、重启后自启检查

服务器重启后所有服务自动恢复,无需手动干预:

| 服务 | 重启后 | 机制 |
|------|--------|------|
| Docker | ✅ 自启 | systemctl enabled |
| derper 容器 | ✅ 自启 | restart=always |
| tailscaled 容器 | ✅ 自启 | restart=unless-stopped |
| Apache 网站 | ✅ 自启 | systemctl enabled |
| IP 转发 | ✅ 恢复 | /etc/sysctl.d/99-tailscale.conf |
| BBR | ✅ 恢复 | /etc/modules-load.d/bbr.conf + sysctl |
| 证书续期 | ✅ 修复后正常 | /etc/cron.d/certbot-derp |

> 客户端(南京电脑)侧:重启后 Tailscale 一般记住 exit node 配置自动重连;
> 万一没走出口,托盘重新选一下 `vm-ubuntu22` 即可(客户端行为,与服务器无关)。

---

## 六、安全提醒

- ⚠️ 容器环境变量中的 `TS_AUTHKEY` 认证密钥在排查过程中被明文带出,
  **建议在 Tailscale 后台轮换该 authkey**。
- `iptables` 中的 `YJ-FIREWALL-INPUT` 链是主机安全软件的恶意 IP 黑名单,
  与本次配置无关,不影响正常访问。

---

## 七、关键路径速查

| 项目 | 值 |
|------|-----|
| 服务器 tailscale IP | 100.93.191.79 |
| 服务器公网 IP | 43.156.23.211 |
| DERP 域名 | derp.stargazer.org.cn |
| DERP 端口 | TCP 33445 / UDP 3478 |
| 证书目录 | /etc/letsencrypt/live/derp.stargazer.org.cn/ |
| derper 证书挂载 | /var/www/html/data/derp-certs/ |
| derper 容器 | fredliang/derper(name: derper, host 网络) |
| IP 转发配置 | /etc/sysctl.d/99-tailscale.conf |
| 续期钩子 | /etc/letsencrypt/renewal-hooks/deploy/derp.sh |
