
现在的开发机大多是 Windows但真正跑编译、跑容器、跑服务的那台机器却常常是 Ubuntu——可能是一台放在角落的物理机也可能是 VMware 里的虚拟机或者是公司机房里分给你的一台服务器。每天靠 scp 传文件、靠 PuTTY 敲命令效率低得让人抓狂。Windows 下 VS Code 远程连接 Ubuntu 并配置免密登录这套组合本质上就是把 VS Code 的编辑器界面留在本地、把代码和运行环境留在远端中间用 SSH 这条隧道连起来再用密钥把每次输密码这一步彻底干掉。整套东西搭好之后你打开 VS Code 就像在本地开发一样终端、调试、文件树全都指向那台 Ubuntu。这篇文章面向的是刚装完 Ubuntu 或者刚拿到服务器账号、想把开发环境理顺的人也适合已经能用密码连上、但每次输密码输到烦的开发者。1. 先把远程开发这件事想清楚我要的到底是什么很多人一上来就装插件、敲命令结果连到一半卡在Setting up SSH Host就懵了。问题往往不在操作而在于没想清楚自己要的是哪种远程。1.1 三种常见连接方式的差别把在 Windows 上开发 Ubuntu 上的代码这件事拆开能走的路其实有三条差别很大。第一种是共享文件夹 本地编辑。VMware 的共享目录、SMB 挂载都算这一类。好处是零配置坏处是文件 IO 走的是虚拟化层node_modules这种几十万小文件的目录遍历一次能让你等到怀疑人生而且编译环境还是在 Windows 上和 Ubuntu 上的运行时不一致。第二种是同步工具。原理是把远端代码拉到本地一份、改完再推回去。它对网络抖动容忍度高但引入了哪一边是准的这个新问题尤其是在远端跑git checkout或者装依赖之后本地那份就废了。第三种就是SSH 远程开发。VS Code 通过 SSH 连到 Ubuntu在远端启动一个 server 进程编辑器的 UI 仍然跑在 Windows 上但文件读写、终端、语言服务、调试器全部在 Ubuntu 侧执行。听起来绕实际体验是最顺的你不需要在两边同步任何东西因为根本只有一份代码。1.2 为什么最后落到 SSH 密钥这条路SSH 这套方案的杀手锏不是能连而是能被自动化。一旦你配好了密钥免密scp、rsync、git、ansible、各种 CI 脚本全都能无交互地跑起来这是密码登录永远给不了的。VS Code 的 Remote-SSH 插件本身就是个ssh命令的封装你在终端里能连通的配置插件一定也能用反过来说——终端里连不上插件里折腾再久也没用。这一点很关键它决定了我后面排查问题的顺序永远先把命令行 ssh 调通再去碰 VS Code。方案环境一致性大目录性能可自动化配置成本共享文件夹差跑在 Windows 侧差一般低文件同步中容易双份不一致好一般中SSH 远程开发好全在远端好好天然支持密钥中一次配好长期受益2. Ubuntu 侧的准备三件不做就白折腾的事Windows 那边再折腾如果 Ubuntu 侧的服务没起来、地址找不到、权限不对一样连不上。我习惯先把这三个基本盘确认掉。2.1 服务端 SSH 是否真的在跑Ubuntu 桌面版默认不装SSH 服务端这一点坑过太多人——他们以为Linux 天生就能 SSH结果 Windows 上一直超时。装和启动一起做sudo apt update sudo apt install -y openssh-server sudo systemctl enable --now ssh systemctl status ssh --no-pager看到active (running)才算数。注意服务名在不同发行版上有ssh和sshd两种写法systemctl的别名通常都能认但脚本里最好写你系统实际存在的那个。还有一个容易忽略的点如果 Ubuntu 开了 ufw 防火墙默认是拒绝入站的。sudo ufw status sudo ufw allow 22/tcp要是你打算把 SSH 端口换成非 22 的比如为了减少扫描噪音记得改/etc/ssh/sshd_config里的Port然后sudo systemctl restart ssh同时别忘了防火墙放行新端口——改完端口没放行、结果自己也连不上这个坑我踩过。2.2 网络可达性IP、网段与那台虚拟机的地址漂移在 Ubuntu 上拿到地址ip -4 addr show | grep inet hostname -I这时候要判断一件很现实的事Windows 和 Ubuntu 是不是在同一个网段。如果 Ubuntu 跑在 VMware 的 NAT 模式下它的 IP 通常能在宿主侧访问到但如果你的 Windows 是通过公司网络、Ubuntu 在另一台物理机上就得确认路由和网段了。真正的麻烦是地址会变。DHCP 租约一到期Ubuntu 的 IP 从192.168.1.105变成192.168.1.113你保存的Host就废了。几个处理思路在路由器上给这台机器的 MAC 做静态绑定最省事或者干脆在 Ubuntu 上配静态 IP如果同一局域网内Ubuntu 装avahi-daemon后可以用主机名.local访问比如ubuntu-dev.localWindows 10/11 原生支持 mDNS 解析配置里写主机名而不是 IPIP 变了也不影响。我个人更倾向第三种主机名本身就带语义ssh ubuntu-dev比ssh 192.168.1.105好记太多。2.3 账号与权限的基本盘免密登录依赖公钥认证而公钥认证对权限极其敏感。提前把这几件事确认好whoami # 确认你要登录的账号 echo $HOME # 确认家目录 ls -ld ~ ~/.ssh家目录和~/.ssh都不能对 group 或 other 可写~/.ssh建议700。这不是洁癖sshd 的StrictModes默认为yes只要它觉得权限太松就会静默拒绝你的密钥——不报权限错误只报认证失败非常难查。3. Windows 侧打通第一条连接Ubuntu 侧就绪之后回到 Windows。这里的顺序是命令行能连了再上 VS Code。3.1 Windows 自带的 OpenSSH 客户端够不够用Win10 1809 之后系统自带 OpenSSH 客户端。打开 PowerShell 敲一句ssh -V有版本号输出就说明能用了。如果提示找不到命令可以在设置 → 应用 → 可选功能里添加OpenSSH 客户端或者用系统包管理器安装。这里有个隐藏的坑很多人电脑上同时装了 Git for Windows它自带一套 MSYS 版本的 ssh。命令行里where ssh出来两三个结果的时候你用的到底是哪个就不确定了。两套的路径写法风格不一样配置文件位置也可能不同。VS Code 的 Remote-SSH 默认调用系统ssh如果你确实想指定另一个可以在设置里搜remote.SSH.path手动指向可执行文件。我一般建议统一用系统自带的少一层心智负担。3.2 手写一份能长期用的 configssh命令支持配置文件Windows 上的路径是C:\Users\你的用户名\.ssh\config。这个文件必须叫config不能是config.txt。用记事本另存为的时候默认会加.txt后缀甚至可能带上 BOM这是新手最常翻的车之一。直接用一个支持纯文本的编辑器新建保存时文件名写成config带引号比较稳。内容大致这样Host ubuntu-dev HostName 192.168.1.105 User yourname Port 22 IdentityFile ~/.ssh/id_ed25519_ubuntu ServerAliveInterval 60 ServerAliveCountMax 3逐行解释一下为什么要这么写Host后面是你给它起的别名之后ssh ubuntu-dev就等价于后面那一整串。VS Code 里显示的远端名字也是它。HostName才是真实地址写 IP 或主机名都行。IdentityFile指定用哪把私钥。一台机器一把钥匙是好习惯后面会讲为什么。ServerAliveInterval 60和ServerAliveCountMax 3是我强烈建议加的两行。不加的话网络中间任何一层设备路由器、NAT 表项把你的空闲连接回收了SSH 要等到 TCP 超时才发现表现出来就是VS Code 挂机一晚上早上回来窗口点不动。加上之后每分钟发一个心跳包断连能被及时感知重连也快得多。如果有多台机器共用一批参数比如同一个用户名、同一个密钥可以用通配符减少重复Host ubuntu-* User yourname IdentityFile ~/.ssh/id_ed25519_ubuntu Host ubuntu-dev HostName 192.168.1.105 Host ubuntu-test HostName 192.168.1.106 Port 2222写完之后先别急着开 VS Code在 PowerShell 里验证ssh ubuntu-dev能进去说明 config 解析正确、网络通、认证过。这一步是整个链条的地基。3.3 VS Code Remote-SSH 的安装与第一次连在 VS Code 的扩展面板搜Remote - SSH作者是 Microsoft 的那个装上。它会带一个Remote - SSH: Editing Configuration Files之类的辅助项一起装。连接流程按F1或者CtrlShiftP打开命令面板输入Remote-SSH: Connect to Host列表里会出现你 config 里写的ubuntu-dev选中新窗口打开左下角状态栏变蓝显示SSH: ubuntu-dev。第一次连接会明显慢因为 VS Code 要在远端下载并安装一个 server 组件放在~/.vscode-server/下。如果你的 Ubuntu 不能直连外网、或者访问软件源很慢这一步会卡在 Setting up SSH Host ... Downloading VS Code Server。应对办法有两个一是让远端能正常访问下载地址二是手动把 server 包下载到本地再传上去解压到~/.vscode-server/bin/commit-id/目录下。commit-id 在连不上时弹出的日志里能看到也可以在 VS Code 的关于里找到对应的提交号。这个操作看着笨但在离线环境里是唯一的路。还有一个硬性门槛值得记住较新版本的 VS Code Server 要求远端 glibc 不低于 2.28。Ubuntu 18.04 的 glibc 是 2.27就会直接报错。这种情况下要么升级发行版要么降级 VS Code 版本没有第三条路。连接成功后文件 → 打开文件夹打开的都是 Ubuntu 上的路径。这里有个新人常犯的错觉左侧文件树里看到的是远端但终端不一定。用 Ctrl 打开终端看提示符里的主机名确认它跑在远端。4. 免密登录密钥的生成、投递与权限闭环到这一步你还得输密码。免密的核心就一件事把本地的公钥放到远端账号的~/.ssh/authorized_keys里。听着简单细节全在细节里。4.1 生成密钥时那几个参数的意义在 Windows 的 PowerShell 里ssh-keygen -t ed25519 -C win-desktop-to-ubuntu -f $env:USERPROFILE\.ssh\id_ed25519_ubuntu拆开看-t ed25519指定算法。现在首选 ed25519密钥短、签名快、安全性好。如果目标机器是特别老的系统OpenSSH 版本很低可能不认 ed25519那就退回-t rsa -b 4096。-C是注释会写进公钥末尾。它不影响认证只影响可读性。多机环境下这个注释非常值钱过两年回头看authorized_keys里一排密钥只靠注释能分辨哪把是哪台机器的。-f指定输出路径。不指定的话默认是id_ed25519会和别的用途撞车。执行时会问你要不要设 passphrase。这里有个取舍设了更安全但每次用都要输等于没免密。想要两者兼得就用 ssh-agent 托管。Windows 上可以启用系统的 ssh-agent 服务Get-Service ssh-agent | Set-Service -StartupType Automatic Start-Service ssh-agent ssh-add $env:USERPROFILE\.ssh\id_ed25519_ubuntu加进去之后同一个登录会话里就不用再输了。注意这是在用户会话级别生效的重启之后需要重新ssh-add除非用某些持久化方案。如果嫌麻烦家庭内网、纯开发环境不设 passphrase 也完全可以接受但你要清楚这个决定意味着什么——私钥文件泄露就等于账号泄露。4.2 把公钥送到 Ubuntu 的三种办法办法一正统的ssh-copy-id。Linux/macOS 上有Windows 的原生 PowerShell 里通常没有。如果你装了 Git Bash可以用它ssh-copy-id -i ~/.ssh/id_ed25519_ubuntu.pub yourname192.168.1.105办法二PowerShell 单行管道。这是我在 Windows 上用得最多的type $env:USERPROFILE\.ssh\id_ed25519_ubuntu.pub | ssh yourname192.168.1.105 mkdir -p ~/.ssh chmod 700 ~/.ssh cat ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys注意用的是typePowerShell 里是Get-Content的别名cat在 PowerShell 里不一定可用。这条命令把建目录、改权限、追加公钥、再改权限一次性做完避免中途权限不对导致后面认证失败。办法三手动粘贴。最原始也最可靠尤其是只有网页控制台可用的时候。先在本地把.pub文件内容复制出来是.pub不是没有后缀的那个私钥文件搞反了是很惨的事故然后在 Ubuntu 上mkdir -p ~/.ssh chmod 700 ~/.ssh vim ~/.ssh/authorized_keys # 粘贴进去一行一把钥匙 chmod 600 ~/.ssh/authorized_keysauthorized_keys里一把钥匙占一行不能换行、不能有多余空格。粘贴的时候如果终端自动折行很容易在中间插进一个看不见的换行符结果就是密钥明明放进去了却认证失败。我的做法是粘完之后用wc -l确认行数一行就是一把钥匙多出来就说明折行了。4.3 权限这事儿差一个位都不行前面提过 StrictModes这里给一个完整的检查清单。在 Ubuntu 上执行ls -ld ~ ~/.ssh ~/.ssh/authorized_keys期望结果大致是路径权限说明~755或750不能对 group/other 可写~/.ssh700只有自己能进~/.ssh/authorized_keys600只有自己能读写私钥Windows 侧只有当前用户可读太开放会被 OpenSSH 拒绝修复命令chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chmod g-w,o-w ~Windows 侧也有对应的坑。Windows 的 OpenSSH 会检查私钥文件的 ACL如果权限太松会直接报UNPROTECTED PRIVATE KEY FILE并拒绝使用。正常的ssh-keygen生成的文件权限是对的但如果你从别处拷贝、解压、或者用某些网盘同步过ACL 可能就变了。修复方式icacls $env:USERPROFILE\.ssh\id_ed25519_ubuntu /inheritance:r icacls $env:USERPROFILE\.ssh\id_ed25519_ubuntu /grant:r $($env:USERNAME):(R)两句的意思是先断开继承来的权限再只授予当前用户读权限。最后验证免密到底成没成用这条ssh -o BatchModeyes -o ConnectTimeout5 ubuntu-dev echo okBatchModeyes会禁止任何交互式提示包括密码提示。如果它返回ok说明免密是真的成了如果它报Permission denied那就是密钥没生效而不是它想问你密码但你没法输。这个判断方式比人肉ssh一次靠谱得多也适合放进脚本做健康检查。5. 连不上时的排查链路从下往上逐层剥SSH 的问题最烦的地方在于报错信息都长一个样Connection refused、Permission denied、Connection timed out。指望报错直接告诉你原因是不现实的得靠分层排除。5.1 先确认网络层通不通Windows 上Test-NetConnection 192.168.1.105 -Port 22看TcpTestSucceeded是True还是False。这一步把问题粗暴地切成两半不通说明根本没摸到 SSH 服务。可能原因有——服务没起来、防火墙拦了、IP 写错了、网段不通、VMware 的网络模式不对。这时候去 Ubuntu 上看systemctl status ssh、ss -tlnp | grep :22在 Ubuntu 本机执行ssh localhost试试本机能连说明服务没问题问题在网络路径。通说明 TCP 握手成功SSH 服务在监听问题出在认证或配置层往下走。我特别想强调Connection timed out和Connection refused的区别超时通常意味着被丢弃防火墙丢包、路由不可达拒绝意味着对方明确回了一个 RST端口没在监听。这两个词指向完全不同的排查方向别混。5.2 再看认证层过不过网络通了还是连不上就上 verbose 模式ssh -vvv ubuntu-dev输出很长但只需要盯几个关键词看到Offering public key: ...然后Server accepts key—— 说明公钥被接受了问题可能在后面比如authorized_keys里还有别的限制项、或者 PAM 层有问题。看到Offering public key: ...然后Authentications that can continue: publickey,password—— 说明服务端拒绝了这把钥匙。这时候去 Ubuntu 上看/var/log/auth.log通常会有一行Authentication refused: bad ownership or modes for file这就是权限问题回到 4.3 节。全程没看到Offering public key—— 说明根本没把钥匙送出去检查IdentityFile路径是否写对、文件是否存在、config 是否被加载ssh -G ubuntu-dev可以打印出最终生效的配置非常好用能验证你的 config 到底被读进去没有。ssh -G这个命令值得单独说一句它不连接只把解析后的完整配置打印出来。当你怀疑 config 没生效、或者某个参数被覆盖了用它一查便知比反复改文件试错高效得多。5.3 最后看VS Code 这一层卡在哪命令行ssh通了VS Code 还连不上问题就在插件侧。常见卡点卡在 Setting up SSH Host—— 多半是远端 server 安装失败网络问题或者 glibc 版本问题回到 3.3 节。报 Could not establish connection ... The process tried to write to a nonexistent pipe—— 这个报错在 Windows 上很典型通常是本地ssh进程和 VS Code 之间的通信出了问题跟远端没关系。常见的触发原因是本地 OpenSSH 版本太老、或者remote.SSH.path指向了一个来路不明的 ssh。升级系统 OpenSSH、清掉那项自定义设置一般就好了。连上了但一会儿就断—— 加ServerAliveInterval见 3.2 节。远端 server 目录损坏—— 表现是连接时反复重试、日志里报各种奇怪的模块加载错误。清理方式ls ~/.vscode-server/bin/ rm -rf ~/.vscode-server/bin/那个可疑的commit-id删掉之后重连VS Code 会重新下载。这招简单粗暴但非常有效我遇到过两次都是这么解决的。还有一个重装系统后必然遇到的问题WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!Ubuntu 重装了、或者 SSH 服务重新生成了主机密钥本地的known_hosts里还记着旧指纹。删掉那条记录就行ssh-keygen -R 192.168.1.105或者用别名ssh-keygen -R ubuntu-dev。注意-R只清理已知主机记录不动你的密钥放心用。5.4 常见报错对照表把上面这些整理成一张表遇到问题先查表再动手能少走很多弯路。现象最可能的原因处理方向Connection timed out服务没起 / 防火墙丢包 / IP 错查systemctl status ssh、放行端口、核对地址Connection refused端口没在监听检查ss -tlnp确认Port配置Permission denied (publickey)权限太松或密钥没投递成功查auth.log按 4.3 节修权限Unprotected private key fileWindows 侧私钥 ACL 太松用icacls收紧权限Remote host identification has changed远端主机密钥变了ssh-keygen -R host连上后长时间空闲就断中间设备回收空闲连接加ServerAliveIntervalSetting up SSH Host 卡住远端 server 下载失败检查外网、手动放置 server 包Server 版本与 glibc 不兼容系统太老升级系统或降级 VS Code6. 用顺手之后的几个长期习惯能连上只是开始用半年一年之后你会发现真正影响效率的是这些细节。6.1 config 的整理与多机管理~/.ssh/config会越长越长我的习惯是给别名加上统一的前缀比如全部用dev-开头dev-ubuntu、dev-test、dev-ci这样在 VS Code 的远程列表里它们会排在一起也不会和其他项目的 Host 混在一起。同一批共用参数用通配符提取到前面具体机器各自覆盖改一处全部生效。另外不要把别名起成ubuntu这种全网通用的名字。你会同时连好几台 Ubuntu回头自己也分不清哪个是哪个。6.2 密钥的复用与轮换一个很实际的建议生产环境和开发环境用不同的密钥不同的团队项目之间也尽量分开。原因不是偏执而是权限回收的现实问题——某个项目的机器要下线了、某个协作伙伴要退出了你只需要把那把对应的公钥从authorized_keys里删掉不影响其他所有环境。如果全网只有一把钥匙那删起来就是全删或者不删。密钥轮换也不用搞得很复杂我的做法是一年检查一次authorized_keys对照每行的-C注释确认来源删掉已经不再使用的。注释这时候就体现出价值了。还有一个细节在 Ubuntu 上也可以反向免密。配置好之后从 Ubuntu 上ssh回 Windows 做文件推送会方便很多需要在 Windows 侧也跑起 sshd 服务。这条不是必需的但做 CI 或者写自动化脚本的时候很有用。6.3 远程侧的插件与终端体验连上远端之后扩展面板会分成本地和远端两栏。理解这个划分很重要语言服务、调试器、代码检查工具要装在远端因为它们需要在代码所在的环境里跑而主题、字体这类纯 UI 的东西装在本地。很多人装完插件发现没生效就是因为装到了本地那一栏。装远端扩展需要远端能访问扩展市场离线环境下就得多一步手动下载安装。关于中文输入法说个反直觉的结论用 Remote-SSH 的时候你不需要在 Ubuntu 上装任何中文输入法。输入法的候选框渲染和上屏是由本地 Windows 完成的远端收到的已经是最终确定的字符。只有在用 X11 转发之类的场景下才需要在远端处理输入法。终端方面Remote-SSH 打开的集成终端默认用远端账号的登录 shell环境变量也是远端的和你在 Ubuntu 本机上开终端一模一样。如果你发现node -v报的版本不对八成是 shell 的初始化文件里 PATH 设置的问题——因为非交互式 shell 不一定会读.bashrc可能有额外的配置需要处理。这个问题和 SSH 本身无关但确实会让人误以为远程环境有问题。我个人在实际操作中的体会是这套东西真正的门槛不在命令有多难而在于变量太多、报错太模糊。把顺序固定下来——先服务端、再网络、再命令行认证、最后才上 VS Code——每次按这个顺序过一遍基本不会有解决不了的问题。另外强烈建议在第一次配好之后把那份config和几条关键命令存成一个笔记因为下次换机器、重装系统的时候你会庆幸自己存过。