背景
家里 homelab 跑了一堆 Docker 服务(CI、Git、数据库等),日常需要拉 GitHub 镜像、访问 Docker Hub、下载海外依赖。单独再搭一套 Clash / sing-box 客户端固然可行,但维护成本高,而且和已有 Tailscale 组网重复。
更轻量的思路:海外本来就有一台能访问全球网络的轻量 VPS(记为 OVERSEAS),国内 Linux 服务器(记为 HOME)也在同一 Tailnet 里——直接把 OVERSEAS 配成 Exit Node(出口节点),HOME 的全局流量经加密隧道从海外出去即可。
本文记录完整落地步骤,以及一个极易踩坑的 Docker + nftables 转发 问题。
目标架构
┌─────────────────────┐ Tailscale 隧道 ┌──────────────────────┐
│ HOME(国内 homelab) │ ────────────────────────► │ OVERSEAS(海外 VPS) │
│ HOME_TAILSCALE_IP │ 自建 DERP 中继或直连 │ OVERSEAS_TS_IP │
│ 原宽带出口 │ │ 海外公网 IP │
└─────────────────────┘ └──────────┬───────────┘
│ exit-node 启用后 │
│ 默认外连经 OVERSEAS 转发 ▼
└──────────────────────────────────────────────► 全球互联网| 角色 | 说明 |
|---|---|
| OVERSEAS | 海外 VPS,宣告 Exit Node,负责 NAT 出境 |
| HOME | 国内服务器,指定 --exit-node 指向 OVERSEAS |
| DERP | 两端无法 UDP 直连时走中继;见下文「DERP 别搞混」 |
和「Nginx 反代桥」的区别:反代桥解决的是「外网用户访问内网服务」;Exit Node 解决的是「内网机器主动访问外网」。方向相反,不要混用方案。
前置条件
- HOME 与 OVERSEAS 已加入同一 Tailnet(
tailscale status互见) - 两端建议用 Docker 跑
tailscale/tailscale,network_mode: host - OVERSEAS 开启 IP 转发:
# /etc/sysctl.d/99-tailscale.conf
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1步骤一:海外机宣告 Exit Node
docker-compose.yml 核心片段:
services:
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale
hostname: overseas-exit
network_mode: host
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_HOSTNAME=overseas-exit
- TS_USERSPACE=false
- TS_EXTRA_ARGS=--advertise-exit-node
volumes:
- ./tailscale-state:/var/lib/tailscale
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
restart: unless-stoppedTS_AUTHKEY 从 Tailscale Keys 生成,写入 .env,不要提交到 Git。
步骤二:管理后台批准(必做)
仅宣告不够,必须在控制台手动批准:
- 打开 Machines
- 找到海外机 → Edit route settings
- 勾选 Use as exit node → 保存
未批准时,客户端会报:
node OVERSEAS_TS_IP is not advertising an exit node批准后可用 CLI 确认:
tailscale exit-node list
# 应出现 OVERSEAS_TS_IP overseas-exit.<tailnet>.ts.net步骤三:国内机指定出口
docker-compose.yml:
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
- TS_EXTRA_ARGS=--exit-node=OVERSEAS_TS_IP --exit-node-allow-lan-access--exit-node-allow-lan-access 很重要:走出口时仍可访问家里局域网(如 192.168.x.x)。
临时切换(不改 compose):
docker exec tailscale tailscale set --exit-node=OVERSEAS_TS_IP --exit-node-allow-lan-access=true
# 恢复国内直连
docker exec tailscale tailscale set --exit-node=验证:
curl -4 ifconfig.me # 应显示海外 VPS 公网 IP
curl -s https://api.github.com/zen步骤四:桌面端(Mac / Windows / 手机)
Linux 服务器可用 compose 持久化 --exit-node;笔记本、手机一般用官方客户端菜单临时切换(重启后常需重选)。
Mac
先处理代理冲突:若本机还开着 Clash Verge、Surge 等 TUN / 系统代理,流量会被它们先接走,Tailscale Exit Node 选了也不生效。要用 VPS 出境,须退出 Clash 或关闭 TUN。
菜单栏操作
- 点右上角 Tailscale 图标
- Exit Node(或「使用出口节点」)
- 选择海外机(如
overseas-exit/OVERSEAS_TS_IP) - 打开 Allow LAN access(走出口时仍可访问家里
192.168.x.x)
命令行(等价)
tailscale set --exit-node=OVERSEAS_TS_IP --exit-node-allow-lan-access=true
tailscale set --exit-node= # 恢复直连验证:浏览器打开 ifconfig.me,应显示海外 VPS 公网 IP。
Windows
- 系统托盘 Tailscale → Exit Node
- 选择海外机,勾选 Allow local network access
tailscale set --exit-node=OVERSEAS_TS_IP --exit-node-allow-lan-access=true同样需先关闭 Clash / Surge 等 TUN 客户端,否则与 Mac 一样不会走 Exit Node。
iOS / Android
Tailscale App → Exit Node → 选择海外机 → 开启 Allow LAN access(若有)。
桌面端 vs 服务器
| 项目 | Linux 服务器 | Mac / Win / 手机 |
|---|---|---|
| 持久化 | TS_EXTRA_ARGS 写进 compose | 菜单切换,重启后需重选 |
| 与 Clash 共存 | 通常无冲突 | 不可与 TUN 代理同时当默认出口 |
| CI / 脚本 | 可删旧 HTTP_PROXY,统一走 Exit Node | 不适用 |
若曾为 Gitea Actions 等配置过 HTTP_PROXY 指向桌面 Clash,Exit Node 落地后应删掉,避免 Mac 关机时 CI 断网。
DERP 别搞混
这是本文最想强调的一点,排障时容易误判。
| 类型 | 说明 |
|---|---|
| 本方案实际走的 | Tailnet ACL 里配置的自建 DERP(derpMap + OmitDefaultRegions: true),不走 Tailscale 官方默认中继 |
| 同机可能还跑着 | 海外 VPS 上另有 derper 容器(如 derp.example.com),若不在 ACL derpMap 里,Exit Node 链路不会走它 |
| 也不是 | derpN.tailscale.com 等官方默认区域(已被 OmitDefaultRegions 关掉时) |
排障时 tailscale status 可能显示 relay "region-a" 之类——那是 ACL 里登记的那台 DERP,不等于海外 VPS 本机 compose 里的 derper 服务。
局域网内有时能 direct 穿透,那是 bonus;跨境多数时候仍经你配置的 DERP。
踩坑:Docker 的 nftables 吃掉 Exit Node 转发
现象
- 控制台已批准 Exit Node
tailscale set --exit-node=...无报错ping OVERSEAS_TS_IP正常ping 8.8.8.8、curl ifconfig.me超时
原因
海外 VPS 同时跑 Docker 时,nftables 的 FORWARD 链默认 policy=drop;而 Tailscale 安装的 ts-forward / ts-postrouting 规则在 iptables-legacy 里。包在 legacy 侧计数增加,但真正转发被 Docker 的 nft 规则丢掉,MASQUERADE 计数一直是 0。
修复
在 DOCKER-USER 链放行 tailscale0,并给 Tailscale 地址段做 NAT:
#!/bin/bash
# /opt/compose/tailscale/fix-exit-node-iptables.sh
iptables -C DOCKER-USER -i tailscale0 -j ACCEPT 2>/dev/null || \
iptables -I DOCKER-USER 1 -i tailscale0 -j ACCEPT
iptables -C DOCKER-USER -o tailscale0 -j ACCEPT 2>/dev/null || \
iptables -I DOCKER-USER 1 -o tailscale0 -j ACCEPT
iptables -t nat -C POSTROUTING -s 100.64.0.0/10 -o eth0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -I POSTROUTING 1 -s 100.64.0.0/10 -o eth0 -j MASQUERADEeth0 换成你海外机真实外网网卡名。建议做成 systemd oneshot,在 docker.service 之后执行,避免重启后规则丢失。
诊断时可对照:
nft list chain ip filter FORWARD # 常见 policy drop
iptables-legacy -L ts-forward -n -v # legacy 侧有计数
iptables -t nat -L POSTROUTING -n -v # 修复后 MASQUERADE 应增长故障速查
| 现象 | 可能原因 | 处理 |
|---|---|---|
not advertising an exit node | 控制台未批准 | 勾选 Use as exit node |
| ping 海外机通、外网不通 | Docker nft FORWARD 丢包 | 执行上文 iptables 脚本 |
| 公网 IP 仍是国内宽带 | exit-node 未生效 | tailscale debug prefs 看 ExitNodeID |
| 走出口后访问不了内网 NAS | 未开 LAN access | 加 --exit-node-allow-lan-access |
| 桌面选了 Exit Node 但 IP 不变 | Clash / Surge TUN 仍启用 | 先关 TUN 或退出客户端 |
| 速度慢 | 经 DERP 中继、海外机带宽小 | tailscale ping 看 direct/relay;大文件优先国内镜像 |
运维建议
- apt / Docker 镜像仍优先配国内源;Exit Node 适合 GitHub、海外 registry 等「必须出境」的场景,别拿 1C0.5G 小机器拉超大镜像。
- 不要在公网裸奔无认证 SOCKS5;Exit Node 加密 + Tailscale 身份,更安全。
- Auth Key、root 密码放环境变量或密码管理器,别写进博客或 compose 仓库。
- Docker 大版本升级或
iptables被重置后出口再断,先重跑fix-exit-node-iptables.sh。
和代理客户端方案怎么选
| 方案 | 适合 |
|---|---|
| Tailscale Exit Node | 已有 Tailnet、要整机一致出境、机器数量少 |
| sing-box / mihomo + 节点 | 要按域名分流、无 Tailscale |
SSH -D | 临时验证链路 |
若你已经在用 Tailscale 做内网穿透或 L4 反代(可参考同目录 小内存海外 VPS 跑 NPM 卡死的迁移实践),Exit Node 是「出站」方向的自然延伸,无需再叠一套代理栈。
小结
- 海外机
--advertise-exit-node+ 控制台批准 - 国内机
--exit-node=OVERSEAS_TS_IP --exit-node-allow-lan-access - 海外机若跑 Docker,务必处理 nft FORWARD 与 tailscale0 的冲突
- DERP 以 Tailnet ACL 为准,别被同机多余的 derper 容器误导
- Mac / Win / 手机:菜单选 Exit Node;与 Clash TUN 二选一;删旧
HTTP_PROXY
按以上步骤,curl ifconfig.me 显示海外 IP、GitHub 可访问,即表示链路正常。
本文为个人 homelab 实践脱敏整理,文中 IP、域名、路径均为示例占位,请按自己的环境替换。

讨论区
欢迎留下想法与补充