最近在 Windows 上使用 VS Code Remote-SSH 连接 Linux 服务器时,遇到了一个比较隐蔽的问题:普通 SSH 登录基本正常,但 VS Code Remote-SSH 会直接失败,日志中出现:
Error: remote port forwarding failed for listen port 10802 过程试图写入的管道不存在。 Failed to parse remote port from server output一开始容易把注意力放在 VS Code Server、SSH 客户端、Windows 管道或者远程安装脚本上,但真正的根因其实是:
SSH 配置中的
RemoteForward端口已经被另一个 SSH 会话占用,而ExitOnForwardFailure yes又会让整个 SSH 连接因此直接退出。
本文记录完整的排查过程,以及后续遇到的 VS Code Remote-SSH / Dev Containers 配置兼容问题。
1 问题现象
本地环境大致如下:
- Windows
- VS Code
- Remote-SSH
- Git for Windows 自带 OpenSSH
- 远端 Linux 服务器
- SSH 配置中使用
RemoteForward - 远端通过
socat将代理端口转给 Docker 容器
SSH 配置类似:
Host my-server HostName <SERVER_IP> Port <SSH_PORT> User <USERNAME> IdentityFile "C:\Users\<USER>\.ssh\id_rsa" RemoteForward 10802 ExitOnForwardFailure yes ServerAliveInterval 30 ServerAliveCountMax 3VS Code Remote-SSH 日志中,真正关键的错误是:
Error: remote port forwarding failed for listen port 10802后面的:
过程试图写入的管道不存在。 Failed to parse remote port from server output实际上只是 SSH 进程提前退出后的连锁报错。
2 先确认 SSH 客户端是否正常
Remote-SSH 会尝试在本机 PATH 中寻找ssh.exe,日志里可能会看到大量:
Checking ssh with "C:\...\ssh.exe -V" Got error from ssh: ... ENOENT这些通常不是问题。
只要最终能找到某个可用 SSH,例如:
C:\Program Files\Git\usr\bin\ssh.exe并成功输出版本:
OpenSSH_x.x就说明本机 SSH 客户端本身是正常的。
所以排查重点应该放到:
remote port forwarding failed而不是前面那些ENOENT。
3 检查远端端口是否已经被占用
在远端服务器执行:
sudoss-lntp|grep10802如果看到类似:
LISTEN 0 128 127.0.0.1:10802 0.0.0.0:* users:(("sshd",pid=123456,fd=9)) LISTEN 0 128 [::1]:10802 [::]:* users:(("sshd",pid=123456,fd=7))说明:
10802已经被某个sshd子进程监听。
也就是说,已经存在一个 SSH RemoteForward 会话。
查看具体进程:
ps-fp123456或者:
ps-aux|grep123456注意,下面这种写法是不对的:
ps-aux|greppid=123456因为ps输出中并不会出现字面量pid=123456。
还可以进一步检查该 SSH 会话对应的网络连接:
sudoss-tnp|grep1234564 为什么 RemoteForward 失败会导致整个 VS Code 连接退出
关键在于 SSH 配置中的:
ExitOnForwardFailure yes它的含义是:
只要端口转发建立失败,SSH 主连接也立即失败。
因此实际流程是:
VS Code ↓ 启动 SSH ↓ 登录远端服务器 ↓ 申请 RemoteForward 10802 ↓ 10802 已经被占用 ↓ RemoteForward 创建失败 ↓ ExitOnForwardFailure yes ↓ SSH 直接退出 ↓ VS Code 远端安装脚本被中断 ↓ 出现“管道不存在” ↓ Failed to parse remote port所以真正的根因依旧只是:
RemoteForward 端口冲突5 为什么换一个 RemoteForward 端口后仍然可能失败
一开始我尝试把:
10802换成:
10812但问题依旧存在。
这时问题就不一定是“旧 SSH 会话占了旧端口”,而可能是:
VS Code Remote-SSH 自己启动了多个 SSH 连接,而每个连接都会重复执行同一个
RemoteForward。
例如:
VS Code SSH connection #1 ↓ 成功监听 10812 VS Code SSH connection #2 ↓ 再次请求监听 10812 ↓ 端口已被 connection #1 占用 ↓ 失败这种情况下,不管把端口改成多少,第二个 SSH 会话都会撞到第一个。
验证方法很简单。
VS Code 连接失败以后,在服务器上立即执行:
sudoss-lntp|grep10812如果新端口此时已经被sshd占用,就说明至少有一个 SSH 会话已经成功建立了 RemoteForward。
6 一个很关键的现象:关闭另一个 VS Code 窗口后就正常了
后来发现:
只要把已经连接服务器的 VS Code 窗口关闭,新连接就能正常建立。
这说明问题和 VS Code Remote-SSH 的 SSH 会话复用方式高度相关。
也就是说,很可能存在这种情况:
VS Code 窗口 A ↓ SSH connection A ↓ RemoteForward 10802 成功 VS Code 窗口 B ↓ SSH connection B ↓ 再次 RemoteForward 10802 ↓ 失败而另一台电脑却没有这个问题,说明两台电脑的 Remote-SSH 配置或者连接模式可能不同。
7 尝试使用 useLocalServer
可以在 VS Code 的settings.json中尝试:
{"remote.SSH.useLocalServer":true}其目的之一,是让多个 VS Code 远程窗口更倾向于复用本地 Remote-SSH 连接,而不是每个窗口都完全独立启动新的 SSH 会话。
对于包含固定RemoteForward的 SSH 配置,这样可以降低重复绑定同一远端端口的概率。
修改以后,建议:
- 完全退出所有 VS Code 窗口;
- 在任务管理器中确认没有残留的
Code.exe; - 重新打开 VS Code;
- 再尝试连接。
8 不要轻易关闭 useExecServer
排查 Remote-SSH 时,有一种常见建议是:
{"remote.SSH.useExecServer":false}这个选项在某些 Remote-SSH 故障中可能确实有帮助。
但是如果你的使用场景是:
本地 VS Code ↓ Remote-SSH 到 Linux 宿主机 ↓ Dev Containers ↓ Attach to Running Container那么关闭useExecServer可能产生新的问题。
我实际遇到的表现是:
运行命令 remote-containers.attachToRunningContainerFromViewlet 错误: 出现未知错误也就是说:
Remote-SSH 本身能用了,但无法继续 Attach 到远端 Docker 容器。
因此,对于 Remote-SSH + Dev Containers 的组合,更合适的配置是:
{"remote.SSH.useLocalServer":true,"remote.SSH.useExecServer":true}或者更简单:
{"remote.SSH.useLocalServer":true}而让useExecServer保持默认值。
9 比较推荐的配置
最终更合理的思路是:
{"remote.SSH.useLocalServer":true}而 SSH 配置保持:
Host my-server HostName <SERVER_IP> Port <SSH_PORT> User <USERNAME> IdentityFile "C:\Users\<USER>\.ssh\id_rsa" RemoteForward 10802 ExitOnForwardFailure yes ServerAliveInterval 30 ServerAliveCountMax 3这样既可以尽量避免多个 VS Code 窗口反复创建 SSH RemoteForward,又不会破坏 Dev Containers over SSH 的使用。
10 更稳定的方案:将代理隧道和 VS Code SSH 解耦
如果不希望 RemoteForward 受 VS Code Remote-SSH 生命周期影响,更稳妥的方法是:
不要把
RemoteForward写在 VS Code 使用的 Host 配置中。
例如把:
RemoteForward 10802 ExitOnForwardFailure yes删掉,只保留普通 SSH 配置:
Host my-server HostName <SERVER_IP> Port <SSH_PORT> User <USERNAME> IdentityFile "C:\Users\<USER>\.ssh\id_rsa" ServerAliveInterval 30 ServerAliveCountMax 3然后单独开一个 SSH 隧道:
&"C:\Program Files\Git\usr\bin\ssh.exe"`-N `-R 10802 `-o ExitOnForwardFailure=yes `-o ServerAliveInterval=30 `-o ServerAliveCountMax=3 ` my-server其中:
-N表示只建立 SSH 隧道,不打开远端 shell。
这时整体结构变成:
Windows │ ├── 独立 SSH tunnel │ ssh -N -R 10802 my-server │ └── VS Code Remote-SSH ssh my-server服务器侧:
127.0.0.1:10802 ↓ SSH tunnel ↓ Windows 本地代理Docker 侧如果需要经过socat:
socat TCP-LISTEN:10801,bind=<DOCKER_BRIDGE_IP>,reuseaddr,fork\TCP:127.0.0.1:10802整体链路:
Docker Container ↓ <DOCKER_BRIDGE_IP>:10801 ↓ socat ↓ 127.0.0.1:10802 ↓ SSH RemoteForward ↓ Windows ↓ 本地代理这个方案最大的优点是:
VS Code Remote-SSH 开几个窗口,都不会影响代理隧道。
对于需要长期使用代理的远程开发环境,我个人更推荐这种解耦方案。
11 排查时最有用的几个命令
检查 RemoteForward 端口:
sudoss-lntp|grep<PORT>查看对应进程:
ps-fp<PID>检查 SSH 网络连接:
sudoss-tnp|grep<PID>手动测试 SSH:
&"C:\Program Files\Git\usr\bin\ssh.exe"-vvv my-server重点关注:
remote forward remote port forwarding failed Address already in use等信息。
12 总结
这类问题最容易误导人的地方,是 VS Code 最终显示的报错往往是:
Failed to parse remote port或者:
过程试图写入的管道不存在但它们经常并不是根因。
如果日志前面出现:
remote port forwarding failed for listen port ...优先检查:
- 远端端口是否已经被其他
sshd占用; - 是否存在旧 SSH 会话;
- VS Code 是否同时启动了多个 SSH 连接;
ExitOnForwardFailure yes是否导致整个 SSH 连接退出;remote.SSH.useLocalServer是否可以改善多窗口连接复用;- 如果还需要 Dev Containers,不要随意关闭
remote.SSH.useExecServer; - 最稳定的做法,是将代理 SSH 隧道和 VS Code Remote-SSH 分离。
最终可以把整个问题归纳为一句话:
RemoteForward 本身没有问题,真正的问题是固定远端监听端口与 VS Code 多 SSH 会话之间发生了生命周期冲突。