VS Code 远程开发链路优化:Tailscale SOCKS5 代理 + SSH 连接复用
历史路径说明:本文记录 2025 年底 VSCode Remote 链路的演进过程(V1→V4),文中 SSH config 为当时的终版,现已被统一方案取代,见多设备命名统一与SSH 密钥网格。
这篇记录我为了让 VSCode Remote 在跨国环境下可用,对网络链路做的几轮调整。
场景:
- 一台工作站在中国,作为主要开发环境;
- 我在德国上学,需要在笔记本上长期远程开发;
- 远程开发主要依赖 VSCode Remote + SSH。
中间经历了几个阶段:
- 直接用 Tailscale,结果长期走 DERP 中继,延迟在 250ms 左右且抖动大;
- 临时用宿舍 Windows 电脑做 SOCKS5 代理,强制把流量走「笔记本 → 宿舍 → 中国」路径;
- 把代理迁移到 24/7 在线的 OpenWrt 路由器上,用 tailscaled 自带 SOCKS5;
- 最后通过
~/.ssh/config把 VSCode/SSH 的体验调顺。
一、设备命名与网络拓扑说明
网络环境和设备清单与桥接 AP 那篇文章一致,这里不再重复;本文只补充和远程开发链路直接相关的角色与命名。
主要设备
-
Linux 工作站(位于中国大陆)
- Linuxmint 系统,主要开发环境
- 安装了 Tailscale
- 对外只开放一个自定义 SSH 端口,例如
55905 - Tailscale IP:
100.100.1.1
-
OpenWrt 路由器(位于德国学生宿舍)
- 红米 AX6(OpenWrt)
- 24/7 在线
- 已接入 Tailscale 网络
- 作为本文最终版代理的承载设备
- Tailscale IP:
100.100.1.4
-
MacBook(便携设备,德国)
- MacBook Air,在宿舍和工位之间通勤使用
- VSCode Remote + SSH 的客户端
- 调试各种链路的主力设备
-
Windows 电脑(位于德国学生宿舍)
- 长期位于德国学生宿舍
- 与 OpenWrt 路由器在同一个局域网
- 中间阶段曾作为临时代理机使用
- Tailscale IP:
100.100.1.6
说明:所有设备已经在同一个 Tailscale 网络里,可以互相 ping 通。后文使用上述自然称呼(如“工作站”、“路由器”、“MacBook”、“Windows 电脑”)。
二、V1.0:只依赖 Tailscale 自己的连通性(DERP 模式)
最开始,我的思路很朴素:既然所有设备都装了 Tailscale,那我直接从 MacBook用 Tailscale 去连工作站就好了。
在 MacBook 上测试:
# 在 MacBook 上执行
tailscale ping 100.100.1.1
输出类似:
pong from workstation (100.100.1.1) via DERP(hk1) in 248ms
pong from workstation (100.100.1.1) via DERP(hk1) in 247ms
...
direct connection not established
从输出里看两点:
via DERP(hk1):说明当前流量是通过 Tailscale 在香港的 DERP 中继节点转发的,未建立点对点直连;~250ms延迟:对于 SSH 只敲命令而言还勉强能用,但对于 VSCode Remote 这种大量小交互、频繁文件同步的场景,体验非常差。
实际体验:
- VSCode Remote 经常在连接/初始化阶段卡住;
- 操作稍微密集一点,就会出现卡顿、断连;
- 事后看,即使加保活配置也只能做到不断线,解决不了卡。
这时我意识到,光能连上撑不起日常开发:延迟和抖动同样要紧。
三、V2.0:用宿舍 Windows 电脑做临时 SOCKS5 代理
在梳理网络拓扑时,我发现了一个有用的事实:
- MacBook ↔ Windows 电脑:Tailscale 能建立直连,延迟很低;
- Windows 电脑 ↔ 工作站:Tailscale 也能建立直连,延迟波动小一些。
也就是说,MacBook ↔ Windows 电脑 ↔ 工作站 这条路径,比直接 MacBook ↔ 工作站 的 DERP 中继要靠谱得多。
只要我把 SSH 流量强制走 MacBook → Windows 电脑 → 工作站 这条路径,就能绕开 DERP,走两段 P2P:
- MacBook → Windows 电脑:德国境内;
- Windows 电脑 → 工作站:德国宿舍 → 中国(跨国,但延迟波动小)。
这就需要在 Windows 电脑上提供一个 SOCKS5 代理端口,然后在 MacBook 的 SSH 配置里使用 ProxyCommand。
3.1 在 Windows 上寻找可用的 SOCKS5 服务
Windows 电脑上已经安装了几种代理相关工具,但实际可用的不多。
几个尝试过程简单列一下:
-
Tailscale 自带 SOCKS5(当时 Windows 上不可用)
tailscaled 本身支持
--socks5-server参数,可以直接开一个 SOCKS5 代理。但当时 Windows 版客户端无法通过 tailscale CLI 启用这个功能,只能放弃。 -
Clash Verge(不适合当前场景)
Clash Verge 的设计偏向作为客户端用远端节点上网,尝试后没有按预期在本机监听固定端口,排除。
-
v2rayN(最终选择)
最后换用了 v2rayN,对它的期待很简单:在本地监听一个 SOCKS5 端口,收到流量后原样直连出去。 配置思路是:
- 入站:一个 SOCKS 入站,监听本地端口(比如 10800);
- 出站:一个 freedom 直连出站,不走任何远端节点;
- 目标是 Tailnet 内地址时,由 Windows 的路由把包交给 Tailscale 虚拟网卡发出。
不使用任何订阅,只做本地转发。
启动后有一点要注意:系统里可能已经有其它
xray.exe进程占住了默认端口(1080),需要通过任务管理器或netstat检查,清理冲突进程,最后确定一个干净的端口,例如10800。
3.2 在 MacBook 上通过 SOCKS5 间接 SSH 到工作站
假设:
- Windows 电脑在 Tailscale 上的 IP:
100.100.1.6 - v2rayN 在 Windows 电脑上监听的端口:
10800
在 MacBook 上可以用 nc 先测一下代理端口是否可达:
nc -vz 100.100.1.6 10800
# Connection to 100.100.1.6 port 10800 succeeded!
然后用 ProxyCommand 做一次 SSH 尝试:
ssh -p 55905 \
-o ProxyCommand="nc -X 5 -x 100.100.1.6:10800 %h %p" \
ruichen@100.100.1.1
这里:
100.100.1.1是 Linux 工作站的 Tailscale IP55905是工作站上 SSH 的监听端口nc -X 5 -x 100.100.1.6:10800 %h %p表示:通过 SOCKS5 代理100.100.1.6:10800去访问目标%h:%p
V2.0 的效果:
- 延迟明显比直接走 DERP 要舒服得多,VSCode Remote 的卡顿情况有所缓解
- 但有一个致命缺点:必须保证 Windows 电脑始终开机,并且 v2rayN 在后台运行。这在宿舍日常环境里并不现实
因此,Windows 电脑一旦关机,代理就随之失效,它只能作为过渡方案。
四、V3.0:用 OpenWrt 路由器提供 24/7 的 Tailscale SOCKS5
真正常驻在线的是 OpenWrt 路由器,也就是宿舍的红米 AX6。它一直通电、功耗低、不用担心有人把它关机。
于是,把代理服务从 Windows 电脑挪到路由器上:
MacBook 始终通过
路由器的 SOCKS5去访问工作站。
4.1 安装 Tailscale:绕过坏掉的 opkg 源
路由器使用的是基于 OpenWrt 的 AE86Wrt 固件。理想状态下,我只需要:
opkg update
opkg install tailscale tailscaled
但现实是:
opkg update报了大量 404 / 签名错误,源已经不再维护;- 内置的应用商店(如 iStore)也无法使用;
- 从 OpenWrt 官方去下载对应架构的
.ipk,也频繁遇到 404。
在确认这是“固件本身的源配置已经老旧”的问题后,我选择了另一条路:直接用 Tailscale 官方提供的静态编译二进制。
大致步骤:
# 1. 下载 tailscale 的 arm64/arm 静态包(根据路由器 CPU 架构选择)
cd /tmp
wget https://pkgs.tailscale.com/stable/tailscale_<版本>_<架构>.tgz
tar xzf tailscale_<版本>_<架构>.tgz
# 2. 把二进制挪到 PATH 下
mv tailscale tailscaled /usr/bin/
chmod +x /usr/bin/tailscale /usr/bin/tailscaled
然后在路由器上登录 Tailscale,加入已有网络(可以用 tailscale up 或事先生成 auth key)。自建控制服务器的用户、密钥和客户端登录方法见Headscale 教程。
4.2 利用 tailscaled 自带 SOCKS5 功能
很多教程会建议在路由器上额外安装 microsocks 之类的软件,但 tailscaled 本身就支持开启 SOCKS5:
tailscaled \
--state=/var/lib/tailscale/tailscaled.state \
--socks5-server=0.0.0.0:1080
这行命令会:
- 启动 tailscaled 并读取/保存状态到指定路径;
- 在所有网口上监听一个 SOCKS5 端口
1080。
只要路由器加入了 Tailscale 网络,MacBook 就能访问路由器的 100.100.1.4:1080(前提是防火墙允许)。
4.3 用 rc.local 做一个够用的开机自启
标准做法是为 tailscaled 写 /etc/init.d/ 脚本,但这台定制固件上调试成本不低,我选了更简单的 /etc/rc.local。
示意:
# /etc/rc.local 中,exit 0 之前加入一行
nohup /usr/bin/tailscaled \
--state=/var/lib/tailscale/tailscaled.state \
--socks5-server=0.0.0.0:1080 \
>/var/log/tailscaled.log 2>&1 &
exit 0
这样每次路由器重启时:
- tailscaled 会自动拉起;
- SOCKS5 端口
1080会自动监听; - 日志写到
/var/log/tailscaled.log,方便排查。
最终,路由器成为一个 24/7 在线的 Tailscale SOCKS5 代理。路由器在 tailnet 中的 IP 为 100.100.1.4,后文 SSH 配置直接引用这个地址。
五、V4.0:通过 SSH 配置把复杂链路封装到一个 Host 名里
到这一步,底层链路跑通了:
MacBook →(Tailscale)→ 路由器(OpenWrt)→(Tailscale)→ 工作站
接下来要解决的是:把复杂度藏起来,让 VSCode/SSH 只感知到一个 workstation。
需求有三个:
- 我不想每次都手写
ProxyCommand=...; - 无论在办公室还是宿舍,VSCode 和 Copilot 看见的都是同一个
Host workstation,以便共享历史、缓存和上下文; - 尽量利用 SSH 的连接复用和保活机制,减少 VSCode 频繁重连的开销。
5.1 最终版 ~/.ssh/config
在 MacBook 上,我最后整理出这样一份配置:
# ================================================================
# Linux 工作站(中国)
# ================================================================
Host workstation
# 工作站的 Tailscale IP
HostName 100.100.1.1
User ruichen
# 工作站上的 SSH 自定义端口
Port 55905
# 所有连接都通过 OpenWrt 路由器的 SOCKS5
ProxyCommand nc -X 5 -x 100.100.1.4:1080 %h %p
# ================================================================
# 全局设置,作用于所有 Host(包括 workstation)
# ================================================================
Host *
# 身份认证
IdentityFile ~/.ssh/id_ed25519
# --- 1. 保活设置:尽量“不断线” ---
# 每 30 秒发一个心跳包
ServerAliveInterval 30
# 理论上允许非常多次失败
ServerAliveCountMax 999999
TCPKeepAlive yes
# --- 2. 性能:连接复用 ---
# 适度压缩,小带宽环境下有帮助
Compression yes
# 启用 SSH 的连接复用(ControlMaster 模式)
ControlMaster auto
# master 连接在后台长期保持
ControlPersist yes
ControlPath ~/.ssh/cm-%r@%h:%p
这份配置的效果是:
- 日常使用时只需要敲
ssh workstation - VSCode Remote 里也只声明一个 Host:
workstation - 所有连接都统一走
MacBook → 路由器:1080 → 工作站:55905,不会因为在宿舍/办公室而改变
5.2 连接复用带来的体验差异
启用 ControlMaster / ControlPersist 后,SSH 的行为会发生一个微妙但重要的变化:
-
第一次连接时,SSH 会建立一个“master”连接,并显示完整的 MOTD 等信息:
ssh workstation # 显示欢迎信息、GPU 状态、登录提示等 -
再次连接时,只要 master 还在,新的连接会直接复用已有通道,不再重新认证,也不会重新打印 MOTD,几乎瞬间进入 shell。
对于 VSCode 来说,这种第二次及之后的连接行为好处明显:
- 建立连接更快;
- 多个终端/扩展可以共享同一条底层 SSH 通道;
- Copilot / 终端历史等,也都建立在同一个
workstation身份之上。
六、常见问题与排查思路
这里单独列一些过程中遇到的问题,便于以后查阅。
6.1 怀疑流量没走代理,仍直连工作站
现象: VSCode 仍然卡顿,怀疑 SSH 流量没走路由器的 SOCKS5。
注意:不能用 tailscale ping 判断:它测的是 Tailscale 的直连路径,代理生效时它照样显示 via DERP,两者无关。
可能原因:
- SSH 没走 SOCKS5,而是 MacBook 直连工作站;
~/.ssh/config中的ProxyCommand被其它配置覆盖、或书写错误。
排查:
- 临时加上
-vvv参数,看 SSH 的调试输出,确认是否执行了ProxyCommand; - 在 MacBook 上用
lsof -nP -iTCP:1080或netstat -an | grep 1080查看本机是否建立了到100.100.1.4:1080的连接。
6.2 路由器上的 tailscaled 没有正确启动
现象:
- MacBook 无法连接
100.100.1.4:1080; - Tailscale 控制台里看不到路由器在线。
排查:
- 在路由器上查看日志文件(例如
/var/log/tailscaled.log); - 使用
ps | grep tailscaled确认进程是否存活; - 手动执行一次启动命令,排除参数问题后再写入
rc.local。
6.3 代理端口被防火墙拦住
在部分路由器固件中,LAN→Tailscale 或本机端口的访问会受到防火墙规则限制。
排查:
- 使用
netstat -lnp | grep 1080确认 tailscaled 在监听; - 确认防火墙中允许来自 LAN/Tailscale 网段访问本机 1080 端口;
- 临时放开相关规则测试,如果可行再收紧到合理范围。
七、几点收尾
回头看 V1.0 到 V4.0,最终生效的配置就两处:路由器上一条 tailscaled --socks5-server=0.0.0.0:1080(写进 rc.local 自启),以及 MacBook 上 ~/.ssh/config 里 Host workstation 的 ProxyCommand 加全局的保活与 ControlMaster 复用。链路固定为 MacBook → 路由器 → 工作站,日常只需 ssh workstation。
后续再加节点(家里的 NAS、GPU 服务器),思路一样:先把拓扑画清楚,再决定每台设备干什么,而不是上来就堆命令。