先说个场景:我日常开发基本都在 WSL 里跑,VSCode 是主力编辑器。换上新设备之后,经常要从外面连回家里那台 Windows 工作站,继续处理 WSL 里的代码。一开始想过直接把 SSH 端口暴露到公网,结果没两天就被各种扫描和暴力破解搞得头大。后来改用 Tailscale 组网,配合 SSH 和 VSCode Remote-SSH,总算把这套远程开发环境弄得比较顺手了。
这套方案解决的核心问题很简单:不需要公网 IP,不需要改路由器,也不用额外购买内网穿透服务,就能让我在任意一台装好 Tailscale 的设备上,安全地 SSH 进入 WSL,然后直接在 VSCode 里写代码、看终端、跑调试。听起来像是一堆工具叠在一起,但实际配置过程并不复杂,只要你理解了 WSL2 的网络隔离方式,剩下就是几条命令和一个转发规则的事。
这篇内容适合已经用过 WSL 和 VSCode、但没试过远程连接的人。我会从原理讲到完整操作,再把踩过的坑列出来,尽量让读者跟着做一遍就能跑通。
1. 这套方案解决的是哪种远程开发痛点
1.1 从实际场景说起
假设你家里有一台 Windows 电脑,平时都在 WSL 里的 Ubuntu 下写代码。某天你带着笔记本出门,想继续改那个项目。正常的思路是远程桌面连回去,但远程桌面不仅占用带宽,而且整个图形界面传过来特别卡,写代码时明显能感觉到延迟。
更符合开发习惯的做法是:本地 VSCode 通过 SSH 连到远端,代码文件在远端,终端也开在远端,但编辑界面显示在本地。这样网络传输的只有键盘输入、文件变更和终端输出,体感接近在本地写代码。要做到这一点,首先得保证本地电脑能访问到那台 Windows 机器,而最让人头疼的就是“访问链路”。
很多人的第一反应是端口映射,把 Windows 上的 22 端口映射到路由器,然后直接用公网 IP 连。但这么做有两个问题:一是路由器往往没有公网 IP,运营商分的是大内网地址;二是即便有公网 IP,SSH 端口暴露在公网之后,只要开一天,日志里就会布满扫描记录,风险非常大。
后来我改用 Tailscale 组网。简单说,Tailscale 能把我所有设备上的客户端登录同一个账号后,互相组成一张私有的虚拟局域网。每台设备都有一个固定的虚拟 IP,设备之间可以直接通信,数据默认走点对点加密链路。外面这台笔记本和家里的 Windows 主机,就像插在同一台虚拟交换机上,互相能 ping 通、能访问端口。
1.2 为什么是 Tailscale + SSH + VSCode
Tailscale 解决的是“网络可达”问题,SSH 解决的是“远程登录”问题,VSCode Remote-SSH 解决的是“远程开发体验”问题。这三层各有分工,缺一不可。
选 Tailscale 而不是其它工具,最重要的原因是它部署简单。Windows 装个客户端,登录账号,另一台设备也装客户端,登录同一账号,两端就通了。不需要自己搭服务器,不需要配置复杂的公私钥对来建立隧道,也不需要在路由器上开端口。对于我这种想快速把环境拉起来的人来说,这比自建 frp 或者研究各种端口映射靠谱得多。
SSH 这层几乎是天然选择。WSL 里的 Ubuntu 自带 openssh-server 安装源,VSCode 也内置了对 SSH 的完整支持。只要远端能提供 SSH 服务,VSCode 就能通过 Remote-SSH 连接过去。对比其它协议,SSH 在安全性、访问控制、密钥管理方面都更成熟。
VSCode Remote-SSH 这个扩展的关键价值在于,它会自动在远端部署一个轻量的 vscode-server,然后本地 VSCode 的界面和远端的文件系统、终端进程做映射。你在本地敲代码,实际上是在远端文件系统里编辑;你在集成终端里跑命令,命令也是真的在远端机器上执行。这就是“远程开发”和“远程桌面”最大的区别,体验非常轻快。
1.3 环境前提与最终效果预览
先说清楚我目前跑通的这套环境,方便你对照:
- 远端:Windows 11,安装了 WSL2,WSL 里是 Ubuntu 22.04
- 本地:另一台 Windows 笔记本,装了 VSCode 和 Tailscale
- 目标:本地 VSCode 通过 SSH 连接到远端 WSL 里的 Ubuntu,直接打开 /home/dev/project 下的项目
这套方案最终的效果是,我在本地 VSCode 里按一下连接,输入一次密钥,几秒后就能看到远程文件夹出现在资源管理器里。打开终端,命令行提示符直接是 Ubuntu 的;跑编译、跑 Python 脚本、断点调试,所有计算都发生在远端。WSL 里安装的 Python、Node、Docker 等等,都能直接使用。
如果你目前没有 WSL,也可以先按常规方式装一个 Ubuntu 发行版。不必追求最新的系统版本,能用就行。另外,需要保证两台设备都安装了 Tailscale 客户端并能登录同一个账号,这是整个链路的地基。
2. 关键原理:读懂 WSL 网络与 Tailscale 组网
2.1 Tailscale 是如何把我所有的设备“拉进同一个局域网”的
应用层看,Tailscale 就是一个组网工具。每台安装并登录了 Tailscale 的设备会被分配一个 100.x.x.x 的虚拟 IP,这个 IP 在账号内全网唯一。设备之间通过加密协议直接通信,优先尝试点对点直连;如果双方网络环境比较特殊、无法直连,则通过官方中继节点转发。
我不打算在这里展开底层协议的细节,只说说实际使用时的几个关键特性。
第一,不需要公网 IP。我的远端 Windows 在一家普通宽带网络后面,本地笔记本也在另一个网络里,两边都没有公网 IP,但 Tailscale 通过 NAT 穿透能力让它们建立了链路。
第二,设备固定 IP 非常有用。WSL 重启后 IP 会变,路由器分配的内网地址也可能变,但 Tailscale 分配给 Windows 主机的 100.x.x.x 地址通常是固定的。我只需要把这个地址写进 SSH 配置,以后就不用管网络变化了。
第三,权限模型简单。同一个账号下的设备默认可以互相访问,但可以在管理后台关闭某些设备之间的访问权限。设备多了以后,这个功能比在防火墙上挨个配规则方便得多。
顺便说一下 Tailscale 的安装,Windows 版直接去官网下安装包,登录后会在系统托盘里出现图标。Linux 版的安装方式也不复杂,下面会提到。安装完成后,用tailscale status命令就能看到当前网络里所有设备的状态和 IP。
2.2 WSL2 的 NAT 隔离问题
这是整篇内容里最容易踩坑的地方。WSL2 的默认网络模式是 NAT,可以把它理解成 Windows 主机内部又运行了一个虚拟机,这个虚拟机有自己独立的 IP 段。在 WSL2 里执行ip addr,你会看到类似172.20.x.x这样的地址,这个地址只有 Windows 主机内部能直接访问,外面其它设备是看不到的。
所以,当本地笔记本通过 Tailscale 连上远端 Windows 主机时,能访问到的是 Windows 主机的 100.x.x.x 地址,而不是 WSL 的 172.20.x.x 地址。要想让本地 SSH 流量进入 WSL,必须做一次端口转发:把到 Windows 上某个端口的 SSH 请求,转发给 WSL 的内部 IP 和 22 端口。
Windows 自带的端口转发工具是netsh interface portproxy。这条命令可以监听 Windows 的某个 IP:端口,然后把数据转发到任意目标 IP:端口。借助它,我就能把“Tailscale IP:2222”映射到“WSL IP:22”。
还有一个需要留意的坑:WSL2 的内部 IP 不固定,每次重启 WSL 都可能改变。如果直接写死一个 WSL IP,过几天可能就失效了。所以端口转发规则不能只配置一次,最好做成一个脚本,在需要的时候重新读一次 WSL 的当前 IP,更新转发规则。
2.3 VSCode Remote-SSH 的工作流程
VSCode 的 Remote-SSH 扩展让“用 GUI 写代码但计算在远端”变成了很自然的事。连接过程大概是:
- 本地 VSCode 根据 SSH 配置发起连接,认证通过后,会在远端执行一系列检查。
- 如果远端没有对应的 vscode-server,VSCode 会根据当前版本自动下载并解压到
~/.vscode-server。 - 连接建立后,本地 VSCode 把远端的文件系统当作工作区,插件列表中标注“SSH: 主机名”的扩展会运行在远端。
这个过程看起来复杂,但用户能感知到的就是:点击连接、等待几秒、打开文件夹。远程环境下,VSCode 的终端、调试控制台、问题面板全都指向远端。
有一点需要理解:VSCode 的很多扩展是需要区分“远程运行”和“本地运行”的。比如 Python 插件,如果想让代码提示和调试都使用 WSL 里的解释器,就必须在远端也安装对应的扩展。VSCode 当你第一次连上远端后,会自动建议安装远程扩展,选择“在远程主机上安装”即可。
3. 完整实操:让外部电脑 SSH 进 WSL 并打开 VSCode
3.1 安装并登录 Tailscale 客户端
第一步,在远端 Windows 和本地笔记本上都安装 Tailscale 客户端。安装过程没什么特别,一路默认即可。安装完成后,打开客户端,用同一个账号登录。
登录后,两台设备会出现在同一个 Tailscale 网络中。可以在 Windows 的命令行里执行:
tailscale status正常结果类似:
100.101.102.103 node-a windows 100.101.102.104 node-b laptop其中100.101.102.103就是远端 Windows 主机的 Tailscale IP。建议把电脑名称改成有意义的名称,比如home-wsl-host和mobile-laptop,方便辨认。改名可以在管理后台操作,也可以在系统设置里改主机名后重新登录。
这一步的关键是确保两端都能看到对方。如果tailscale status里看不到对方,可以尝试在客户端里确认登录账号是否一致,或重新登录一次。
3.2 配置 WSL 的 SSH 服务
接下来进入 WSL 的 Ubuntu,安装并启动 ssh 服务:
sudo apt update sudo apt install openssh-server -y sudo service ssh start如果 WSL 里启用了 systemd,还可以执行:
sudo systemctl enable ssh sudo systemctl start ssh但 WSL 默认不一定启用 systemd,所以我更习惯用service ssh start。为了确保每次启动 WSL 后 SSH 都能自动运行,可以在 WSL 中编辑/etc/wsl.conf,加入类似下面的配置:
[boot] command = service ssh start需要说明的是,WSL 默认的配置文件位置和 systemd 支持情况在不同 Windows 版本上略有差异,如果你的wsl.conf里已经有内容,不要覆盖,只需要增加[boot]段落。
检查 SSH 是否正常监听:
ss -tlnp | grep :22只要看到类似0.0.0.0:22或:::22的监听记录,就说明 sshd 已经在 WSL 内部工作了。
这里有个容易忽略的细节:WSL 里的 sshd 默认监听在 WSL 的虚拟网卡上,这不等于 Windows 的 Tailscale 网卡也能直接访问它。所以下一步的端口转发是必须的。
3.3 用端口转发打通 Tailscale 网卡到 WSL 的通道
在 Windows 管理员命令行中,先查看当前 WSL 的 IP:
wsl hostname -I输出通常是一个或几个 IP,取第一个即可。假设这里得到的 WSL IP 是172.20.10.5,远端 Windows 的 Tailscale IP 是100.101.102.103,那么执行:
netsh interface portproxy add v4tov4 listenaddress=100.101.102.103 listenport=2222 connectaddress=172.20.10.5 connectport=22这条命令的意思是:当外部客户端访问 Tailscale IP 的 2222 端口时,Windows 会把流量转发到 WSL 的 172.20.10.5:22。
为什么不用 22 作为监听端口?因为 Windows 本身也可能运行 OpenSSH Server,如果用 22,很容易冲突;另外用一个不常见的端口,也方便我在日志里确认走的是哪条转发规则。
配置完成后,查看当前转发列表:
netsh interface portproxy show all然后需要确认 Windows 防火墙允许入站 2222 端口。理想的做法是只允许来自 Tailscale 网段的流量访问,可以执行:
netsh advfirewall firewall add rule name="WSL-SSH-Tailscale" dir=in action=allow protocol=TCP localport=2222 remoteip=100.64.0.0/10100.64.0.0/10是 Tailscale 默认使用的地址段,这样其他来源的请求会被防火墙挡住,避免把 SSH 端口暴露到公网上。
做完这一步,先在本地笔记本上用命令测试:
ssh -p 2222 dev@100.101.102.103其中dev是 WSL 里的用户名。如果能登录成功,说明整条链路已经通了。
3.4 编写自动化脚本应对 WSL IP 变化
端口转发规则里写死的 WSL IP 在 WSL 重启后大概率会变,所以需要自动更新。我写了这样一个 PowerShell 脚本,保存为update-wsl-forward.ps1:
$listenIP = "100.101.102.103" $listenPort = "2222" $targetPort = "22" $wslOutput = wsl hostname -I $wslIP = $wslOutput.Trim().Split(' ')[0] if (-not $wslIP) { Write-Host "Failed to get WSL IP" exit 1 } netsh interface portproxy delete v4tov4 listenaddress=$listenIP listenport=$listenPort | Out-Null netsh interface portproxy add v4tov4 listenaddress=$listenIP listenport=$listenPort connectaddress=$wslIP connectport=$targetPort Write-Host "Port proxy updated to $wslIP"把这个脚本放到一个固定目录,比如C:\Scripts\update-wsl-forward.ps1,然后在 Windows 的任务计划程序里创建一个任务:
- 触发器:登录时
- 操作:启动 PowerShell,参数为
-ExecutionPolicy Bypass -File C:\Scripts\update-wsl-forward.ps1 - 勾选“使用最高权限运行”
这样每次 Windows 登录后,端口转发规则会自动更新到当前 WSL IP,不需要手动维护。
如果你不想用计划任务,也可以在 WSL 的.bashrc里调用 Windows 命令,但那样需要从 WSL 里以管理员权限调用 PowerShell,反而更麻烦。Windows 计划任务是最省心的方式。
3.5 在 VSCode 中创建远程连接并开始编程
本地笔记本的 VSCode 里安装Remote - SSH扩展。安装后,按F1打开命令面板,输入Remote-SSH: Connect to Host...,选择Add New SSH Host或者直接编辑 SSH 配置文件。
我通常直接编辑~/.ssh/config,加入:
Host wsl-remote HostName 100.101.102.103 User dev Port 2222 IdentityFile ~/.ssh/id_ed25519建议提前配置好 SSH 密钥,避免每次输入密码。在本地生成密钥:
ssh-keygen -t ed25519然后把公钥内容追加到 WSL 的~/.ssh/authorized_keys文件里,并确保权限正确:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys配置好之后,在 VSCode 命令面板里选择wsl-remote连接。第一次连接时,远端会自动安装 vscode-server,耐心等待一下,连接成功后左下角会显示类似SSH: wsl-remote的状态。
此时打开远程文件夹,找到/home/dev/project,就能开始写代码了。远程终端默认也是 WSL 里的 shell,编译和调试都发生在远端。
4. 备选路径:在 WSL 内部直接运行 Tailscale
4.1 什么时候适合这种方案
前面说的端口转发方案在绝大多数环境下都能跑通,但它要求 Windows 主机和 WSL 之间做一层转发。如果你不希望额外维护这个转发规则,也可以在 WSL 内部直接安装 Tailscale,让 WSL 自己也成为一个 Tailscale 节点,这样客户端可以直接访问 WSL 的 100.x.x.x 地址,而不需要经过 Windows 的 portproxy。
这个方案有一个前提:WSL 需要能成功启动 Tailscale。WSL2 的网络栈比较特殊,有些版本下 Tailscale 需要以用户态网络模式运行,速度上会有一些损失。如果只是开发调试,影响并不大。
4.2 配置方法与注意事项
在 WSL 里执行:
curl -fsSL https://tailscale.com/install.sh | sh sudo tailscale up登录方式和 Windows 客户端类似,浏览器会打开一个认证页面,确认后 WSL 就成为一个新节点。再用tailscale status查看,会看到 WSL 的分配 IP。
此时 sshd 已经在 WSL 里监听,直接使用本地笔记本的 Tailscale 客户端,连接 WSL 节点的 IP 和 22 端口即可。VSCode 的 SSH 配置变成:
Host wsl-native HostName 100.x.x.x User dev Port 22 IdentityFile ~/.ssh/id_ed25519这个方案的好处是少一层 Netsh 转发,网络路径更短。缺点是如果 WSL 重启,Tailscale 节点可能需要重新登录或等待连接恢复;另外,WSL 里的 Tailscale 节点和 Windows 里的 Tailscale 节点会同时存在,管理后台里需要分清哪个是哪个。
我个人的建议是:优先用前面那套 Windows 客户端 + 端口转发的方案,因为它更接近常规环境下的部署方式;WSL 内直装 Tailscale 可以作为备选,遇到转发规则始终无法生效时再试。
5. 常见问题与排障记录
5.1 连不上:先判断问题出在组网、端口还是 SSH
远程连接失败是最常见的情况,我的排查思路分三步:
第一步,确认 Tailscale 网络是否正常。在本地笔记本上执行:
tailscale ping 100.101.102.103如果tailscale ping能通,说明组网没问题。如果连不上,去看两台设备的 Tailscale 状态是否在线、是否登录同一账号。
第二步,确认 Windows 端口转发是否生效。在远端 Windows 上执行:
netsh interface portproxy show all看是否有100.101.102.103 2222对应的记录。如果规则存在,再确认防火墙规则是不是把来源限制得太严格,导致 Tailscale 网卡的请求未被放行。
第三步,确认 WSL 的 sshd 是否在监听。在 WSL 里执行ss -tlnp | grep :22。如果没有任何输出,先启动 SSH 服务:
sudo service ssh start如果服务状态正常但依旧连不上,可以临时把防火墙规则关掉做一次对比测试,确认问题在防火墙后再重新添加规则。
5.2 WSL 重启后端口转发失效
这个问题几乎一定会遇到,因为你手动配置的 portproxy 规则绑定了旧的 WSL IP。WSL 重启后,旧 IP 失效,新 IP 生成,但 Windows 的转发规则还指向旧 IP。
解决办法就是前面写的自动化脚本。如果你还没有配置计划任务,手动执行一次脚本即可临时恢复。如果你发现执行脚本后依然连不上,先检查wsl hostname -I命令是否输出正确地址。某些情况下,如果同时安装了多个 WSL 发行版,输出会有多行,脚本里要确保取到目标发行版对应的 IP。可以在 WSL 中直接执行hostname -I确认。
5.3 VSCode 远程连接卡在部署 vscode-server
第一次连接时,VSCode 需要在远端下载 vscode-server。如果网络状态不够好,下载可能很慢或直接中断。
一种处理方法是:在远端 WSL 里手动下载并解压 vscode-server。具体步骤如下:
- 在本地 VSCode 的输出面板里,找到 Remote-SSH 的输出日志,里面通常会显示一个下载链接。
- 复制链接,在 WSL 里用
wget或curl下载。 - 根据日志中提示的 commit ID,把解压出来的文件放到
~/.vscode-server/bin/<commit-id>目录。 - 重新连接。
如果不想手动处理,也可以先在 WSL 里确保curl、wget和tar可用,然后多试几次连接。有些版本的 VSCode 会因代理设置而卡住,检查环境变量中的代理相关配置是否有误。这里要特别提醒:不要配置任何不安全的代理来源,保持系统默认即可。
5.4 登录过期、节点掉线如何处理
Tailscale 客户端偶尔会出现登录状态过期,尤其是长期不用的设备。现象是tailscale status里节点显示-或离线,客户端图标提示需要重新认证。
处理方式很简单:打开 Tailscale 客户端,点击重新登录,或者命令行执行tailscale up,按浏览器提示完成认证。如果是远端 Windows 上的客户端掉了,本地会连不上;如果是本地笔记本客户端掉了,VSCode 会提示网络不可达。
另外,如果在管理后台删除了节点,客户端需要重新执行登录流程才能恢复。这种操作我一般会避免,因为节点名称一旦删除,重新加入后的 IP 可能变化,和 vscode-server 的关联也会重建,没有额外收益。
5.5 使用体验优化与安全建议
远程开发体验很大程度取决于网络延迟。如果你和我一样,家里的宽带和笔记本所在的网络之间有较高的延迟,可以试试这几个优化:
- 在 SSH 配置里加上
ServerAliveInterval 30和ServerAliveCountMax 3,避免连接因空闲超时被断开。 - 远程安装扩展时只装必要的,不要一股脑把本地所有扩展都同步到远端,因为每个扩展都会增加 vscode-server 的负担。
- 关闭不需要的自动保存文件监听,减少远端文件系统事件通知对性能的影响。
安全方面,我的习惯是:
- 始终使用 SSH 密钥,不使用密码登录,并关闭密码认证。
- 防火墙只放行 Tailscale 网段到 2222 端口,不要放行全部来源。
- 定期检查 Tailscale 管理后台的节点列表,删除不认识的设备。
- WSL 的 sshd 里禁止 root 登录。
最后分享一个小技巧:我习惯把 WSL 下的项目目录建在/home/dev/project而不是/mnt/c/...,因为跨文件系统读写会有额外性能损耗,远程 VSCode 打开项目时差异更明显。把项目放在 WSL 原生文件系统里,编译速度和文件监听都会更快。这套环境跑通之后,我甚至可以在地铁上打开笔记本,连回家里 WSL 里的项目,继续改代码,VSCode 的远程体验和我坐在工作站前几乎没有区别。