在 Windows 上写代码、在 Ubuntu 上跑环境,这个组合我用了好几年,最开始那阵子我是靠 Samba 共享目录加手动同步文件过日子的,改一个配置要来回切窗口,跑个脚本还得复制粘贴到终端里,效率低到让人怀疑人生。后来换成 VS Code 远程连接 Ubuntu,再把免密登录配好,整个工作流才算真正顺起来——本地 Windows 保留顺手的外设和熟悉的窗口管理,Ubuntu 那边负责提供 Linux 原生编译环境、Docker、Python 生态和各种只有 Linux 才跑得舒服的依赖,两边各干各的活,中间靠一条 SSH 隧道连起来。这篇文章聊的就是这套方案从零到能用的完整过程:VS Code 远程连 Ubuntu,然后配置免密登录,让你每次打开都不用手打密码。适合刚装完 Ubuntu 的新手,也适合已经在用远程连接、但每次连接还在输密码、想把这步优化掉的同学。下面按准备、配置、实操、踩坑四个层面拆开讲,能抄的地方我直接给命令和配置。
1. 整套方案的思路与组件分工
1.1 为什么选 Remote-SSH 而不是别的远程方式
远程开发的方案其实不少,VNC、RDP 这类图形化远程桌面走的是"把整个桌面搬过来"的路线,带宽吃得多,拖动窗口有延迟;共享目录走的是"文件同步"的路线,环境还是本地的,遇到 Linux 专属依赖就抓瞎;还有一种是把代码放本地用 SFTP 上传,改一次传一次,版本一乱就分不清哪边是最新的。Remote-SSH 的思路和它们都不一样,它把 VS Code 拆成两半:界面部分跑在你的 Windows 上,负责渲染、输入、快捷键;后端服务部分跑在 Ubuntu 上,负责文件读写、终端执行、语言服务、调试器。你看到的文件树其实是 Ubuntu 上的真实文件,你在编辑框里敲的每一行都会直接落到目标机器的磁盘上。
这样带来的好处很实在。第一,文件只有一份,不存在本地和远程不同步的问题;第二,扩展可以分端安装,像 Python、C/C++、Go 这类需要在目标平台跑语言服务的扩展会装在远程端,真正利用 Ubuntu 的编译器和解释器;第三,终端是原生的 Linux shell,apt、make、gcc、docker 全都能直接用,不用担心 Windows 上那些路径转义和换行符的破事。
代价也有:连接要稳定,网络一抖就可能掉线;首次连接需要在远程端下载服务端组件,如果机器访问外网不方便,这一步会卡住。但总体来说,只要 SSH 通了,这套方案是我试过的性价比最高的组合。
1.2 三块拼图:SSH 服务、密钥对、VS Code 插件
整套流程拆开看只有三块内容,理清楚它们的关系,后面出问题就知道该往哪儿查。
| 组件 | 所在位置 | 作用 | 出问题的典型表现 |
|---|---|---|---|
| openssh-server | Ubuntu | 监听 22 端口,接受连接请求 | 端口拒绝、连接超时 |
| 密钥对 | 私钥在 Windows,公钥在 Ubuntu | 免密登录的凭证 | 每次连接仍提示输密码 |
| Remote-SSH 插件 | Windows 的 VS Code | 建立隧道、拉起远程服务 | 卡在"正在打开远程" |
SSH 服务是地基,密钥对是钥匙,插件是门。地基没打好,门装不上;钥匙配错了,每次进门都得敲门让人开。很多人配免密失败,问题往往不在插件,而在公钥权限或者 sshd 配置上,所以后面我会把 Ubuntu 端的细节讲透。
提示:如果你现在连 SSH 都连不上,先别管免密,先把最基础的用户名密码登录打通,再回头来配密钥。一步一步来,出问题才好定位。
1.3 开始之前需要确认的几件事
动手前先花两分钟把这几个前提确认掉,能省掉后面一半的排查时间。第一,Ubuntu 得有一个能登录的账号,并且知道密码,后面要用它做一次密码登录来下发公钥。第二,两台机器要在能互相访问的网络里,同一局域网最省事,跨网段就要看路由和端口映射了。第三,要拿到 Ubuntu 的 IP 地址,用ip addr或者hostname -I都能查,记住那个形如 192.168.x.x 的地址。第四,Windows 这边的 PowerShell 或者终端能正常使用,后面生成密钥要用到。
还有一点容易被忽略:确认 Ubuntu 那台机器的系统时间是不是准的。SSH 握手对时间不敏感,但有些认证机制会受影响,而且时间乱掉通常意味着别的地方也有问题,顺手看一眼没坏处。
2. Ubuntu 端 SSH 服务的安装与调优
2.1 装 openssh-server 并确认它在跑
Ubuntu 桌面版默认只装了客户端,服务端要手动装。登陆 Ubuntu,打开终端,执行:
sudo apt update sudo apt install -y openssh-server装完之后看服务状态:
systemctl status ssh正常的输出里会有active (running)字样。如果显示的是inactive或者根本找不到这个服务,先看看是不是装失败了。顺手记一下,这个服务名在 Ubuntu 上叫ssh,不是sshd,重启命令是sudo systemctl restart ssh,别敲错了。
再看一眼端口监听情况:
ss -tlnp | grep :22有输出说明 22 端口已经处于监听状态。如果你的 Ubuntu 是用虚拟机装的,这里还有一个大坑:虚拟机的网络模式如果是 NAT,宿主机访问虚拟机需要做端口转发,或者干脆改成桥接模式,让虚拟机在局域网里有一个独立 IP,省掉一堆麻烦。我早期用 NAT 模式折腾了半天端口映射,后来改用桥接,世界一下就清净了。
2.2 sshd_config 里真正值得改的几个参数
配置文件在/etc/ssh/sshd_config,改之前先备份:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak这个文件里参数很多,但大多数默认值就够用,真正值得关注的是下面这几个。
第一个是PubkeyAuthentication,免密登录靠它,默认是 yes,确认一下别被注释掉或者改成 no。
第二个是PasswordAuthentication,配免密的时候先保持 yes,因为你要用一次密码登录把公钥送上去。等免密验证通过之后,可以按需决定要不要关掉,但关掉之前一定要确认密钥登录确实能用,否则会把自己锁在门外。
第三个是AuthorizedKeysFile,它决定去哪里找公钥,默认值是.ssh/authorized_keys,一般不用动,但如果你看到有人把它改成别的路径,公钥就得放到对应位置去。
第四个是PermitRootLogin,不建议用 root 直接连,日常用普通账号,需要提权的时候 sudo 就行。
改完之后一定要先做语法检查再重启服务,这一步千万别省:
sudo sshd -t没有输出就是配置没问题,有输出会告诉你哪一行出了什么错。然后:
sudo systemctl restart ssh注意:
sshd -t这个检查必须养成习惯。配置文件写错一个字母,重启服务就失败,如果你这时候又刚好把当前连接关了,那就只能去物理机前面敲键盘了。
2.3 网络连通性和防火墙别漏掉
服务跑起来了不代表外面能连进来。先在 Ubuntu 上看防火墙状态:
sudo ufw status如果显示Status: active,那就要放行 22 端口:
sudo ufw allow 22/tcp如果显示Status: inactive,那这一层就不用管了。不过云主机的情况不太一样,云服务商那边通常还有一层安全组或者网络访问控制,需要去控制台里手动放行 22 端口,这个在本地虚拟机上不存在,但用云主机的人经常忘掉。
验证连通性最直接的办法是在 Windows 上 ping 一下:
ping 192.168.x.x能通说明网络层没问题。ping 不通的话,先检查两台机器是不是在同一个网段,再用ip addr核对一下 IP 有没有看错。有些网络环境会屏蔽 ICMP,ping 不通但 SSH 能连,所以 ping 通不通只是一个参考,最终还是要靠 SSH 连接来验证。
3. 免密登录的原理与完整配置
3.1 非对称加密到底谁在验谁
免密登录用的是非对称加密,一对密钥,私钥和公钥。私钥留在你自己的 Windows 上,绝对不能外传;公钥放到 Ubuntu 上,谁都能看,不保密。登录的时候,Ubuntu 拿你留在它那儿的公钥出一道只有对应私钥才能答对的题,你的 Windows 用私钥算出答案发回去,对上了就放你进来,全程密码不出场。
用一个不太严谨但好记的类比:公钥像是一把锁,你把它挂在 Ubuntu 的门上;私钥是唯一能开这把锁的钥匙,一直揣在自己兜里。别人拿到锁也开不了门,因为钥匙不在他手上。所以整个流程的关键就两件事:一把好钥匙,和一把挂对位置的锁。
注意:私钥文件的权限必须是只有你自己能读。在 Linux 上 SSH 会强制检查这一点,权限太开放会直接拒绝使用。Windows 这边相对宽松,但也不能随便丢在共享目录里。
3.2 在 Windows 端生成密钥对
打开 PowerShell,先看看有没有现成的密钥:
ls ~/.ssh如果没有id_rsa或者id_ed25519这类文件,就生成一个。现在推荐用 ed25519,比传统的 RSA 更短、更快,安全性也不差:
ssh-keygen -t ed25519 -C "your_email@example.com"执行后会问你保存路径,直接回车用默认值就行,会生成在C:\Users\你的用户名\.ssh\下面。然后会问你设不设密码短语,这里就是"免密"和"更安全"的取舍点了。
设了密码短语,私钥被偷走也用不了,但每次连接都要输一遍,那免密的意义就打了折扣,除非你配合 ssh-agent 把密码缓存在内存里。不设的话,私钥文件本身就是唯一凭证,谁拿到谁能登,所以文件权限和电脑本身的安全性就更重要。我个人的做法是:自己的开发机、只有自己用,不设;如果是共用电脑或者安全要求高的场景,设上,然后用代理来缓存。
生成完之后ls ~/.ssh应该能看到两个文件,一个是id_ed25519(私钥),一个是id_ed25519.pub(公钥)。认准带.pub后缀的那个,那是要送到 Ubuntu 上去的。
3.3 把公钥送上去并锁死权限
送公钥最省事的办法是ssh-copy-id,但 Windows 上这个命令不一定有,所以我一般用更通用的方式。先在 PowerShell 里把公钥内容打出来:
cat ~/.ssh/id_ed25519.pub复制那一整行输出,注意从头到尾别漏字符也别多空格。然后 SSH 登录到 Ubuntu,在目标账号下执行:
mkdir -p ~/.ssh chmod 700 ~/.ssh vi ~/.ssh/authorized_keys把刚才复制的那行公钥粘贴进去,保存退出。然后权限收紧:
chmod 600 ~/.ssh/authorized_keys这里三个权限数字要记牢:.ssh目录是 700,只允许所有者读写执行;authorized_keys是 600,只允许所有者读写。权限不对是免密失败最常见的原因之一,SSH 会认为这个文件不可信,直接跳过公钥认证,回退到密码。
如果 Ubuntu 上装了 ssh-copy-id 而在 Windows 上又想偷懒,也可以在 PowerShell 里用一条管道命令:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh user@192.168.x.x "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"这条命令的意思是把本地公钥内容通过 SSH 管道送过去,追加到远程的 authorized_keys 里。第一次执行会要求输密码,输完之后公钥就到位了。不过追加操作不会自动帮你处理权限,所以传完还是要登上去确认一下 chmod 有没有做对。
验证免密是否生效,直接在 Windows 上连一次:
ssh user@192.168.x.x如果这次没有提示输密码就进去了,说明配置成功。如果还要密码,就用-v参数看详细日志:
ssh -v user@192.168.x.x日志里会明确告诉你公钥认证是成功还是被拒绝了,被拒绝的原因通常也会写出来,顺着找基本都能定位。
3.4 写一份能长期用的 ssh config
每次连接都要敲一长串ssh user@192.168.x.x,时间长了也烦,而且 VS Code 远程连接同样要用到这个信息。解决办法是在 Windows 上写一个 config 文件,路径是C:\Users\你的用户名\.ssh\config,没有就新建一个:
Host dev-ubuntu HostName 192.168.x.x User yourname Port 22 IdentityFile ~/.ssh/id_ed25519 ServerAliveInterval 30 ServerAliveCountMax 6配好之后,连接只需要:
ssh dev-ubuntu这里有两个参数值得单独说。ServerAliveInterval是每隔 30 秒给服务器发一个保活包,ServerAliveCountMax是连续 6 次没回应才断开,算下来大概三分钟没有任何响应才会判定掉线。这两个参数对于防止长时间空闲被防火墙掐断连接特别有用,尤其是那种一挂机就被断开的环境,加上之后稳定很多。
提示:config 里给主机起一个好记的别名很重要,VS Code 的 Remote-SSH 会直接读取这个文件,别名会出现在它的主机列表里,点一下就连接,不用记 IP。
4. VS Code 侧的连接与远程环境
4.1 插件安装和第一次连接
在 Windows 的 VS Code 里打开扩展面板,搜索 Remote-SSH,安装 Microsoft 官方那个。装完之后,左侧活动栏会多出一个远程连接的图标,点开,鼠标悬停在 SSH 那一栏上,能看到加号和齿轮图标。
一般不需要手动添加主机,因为插件会读取你刚写的 ssh config 文件。点齿轮进入设置,或者直接点刷新,配置好的主机别名就会出现。鼠标移到主机名右边,点那个新建窗口连接的图标,VS Code 就会开始连接过程。
首次连接会弹出一堆东西:询问远程主机的操作系统类型,选 Linux;询问是否信任这个主机,选继续;然后它会在远程端下载并安装一个 VS Code Server,这一步需要远程机器能访问下载源,网络好的话几十秒就完事。
连接成功后,左下角的状态栏会出现类似SSH: dev-ubuntu的字样,这时候打开文件夹,看到的就是 Ubuntu 上的真实目录结构了。终端菜单里打开新终端,跑的是远程的 shell,uname -a会显示 Linux 内核信息,确认一下没连错机器。
4.2 首次连接在远程端做了什么
了解这一步能帮你排查很多连接卡住的问题。VS Code 不是凭空变出远程能力的,它会在远程机器上做这么几件事:第一,检查~/.vscode-server目录,如果不存在或者版本对不上,就从服务器下载对应的服务端程序;第二,解压并启动一个 node 进程;第三,本地和远程之间建立一个端口转发,把通信跑在 SSH 隧道里。
所以卡在"正在打开远程"这类提示时,方向就很清楚了:要么是远程端下载失败,要么是服务端进程起不来,要么是隧道建立不了。用ps aux | grep vscode在远程端看进程有没有起来,看~/.vscode-server目录里有没有内容,基本就能判断卡在哪一环。
如果远程机器网络受限,下载服务端会一直失败。这种时候可以在本地的 VS Code 设置里找到 Remote-SSH 的相关选项,配置一个能访问的下载地址,或者在能联网的机器上把服务端包提前下好,手动放到对应的目录里。虽然麻烦,但比一直卡着强。
4.3 远程端扩展和本地扩展要分开看
连接远程之后,扩展面板会分成两块:本地安装的和远程安装的。这个设计很有讲究,因为不是所有扩展都适合装在远程。
Python、C/C++、Go、Rust 这类语言支持扩展,需要在远程装,因为它们要调用目标平台上的解释器、编译器和调试器,装在本地没用。主题、图标、快捷键、大部分纯界面类的扩展装在本地就行,装了远程反而浪费资源。还有个别的扩展支持在两边都装,按需选择。
实操里最常遇到的坑是:本地看着扩展都在,连上远程发现代码没有语法高亮、没有补全。这时候去扩展面板看看远程那一栏是不是空的,把语言扩展在远程端重新装一遍就好了。判断的标准很简单——需要执行目标平台程序的,装远程;只影响界面外观的,装本地。
5. 踩坑实录与排查速查表
5.1 连接类问题的定位顺序
连不上是最常见的问题,别一上来就瞎改配置,按下面的顺序查,基本三步内能定位。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| Connection refused | SSH 服务没跑或端口不对 | systemctl status ssh,确认 22 端口监听 |
| Connection timed out | 网络不通或被防火墙拦 | ping 测试,检查 ufw 和云安全组 |
| Permission denied | 用户名或认证方式不对 | 用-v看日志,确认公钥是否被接受 |
| 一直提示输密码 | 免密没生效 | 检查权限 700/600 和公钥内容是否完整 |
| 卡在打开远程 | 服务端下载失败 | 看远程~/.vscode-server目录 |
这里有几个我自己踩过的细节。公钥粘贴的时候,如果是从网页或者聊天工具复制的,末尾可能带上了换行或者空格,粘进去就废了,所以一定要用纯文本编辑器打开公钥文件直接复制内容。还有一种情况是authorized_keys里有多行内容,某一行格式错了会导致整个文件解析出问题,建议一行一行确认。另外,ssh-copy-id在 Windows 上的缺失确实是个小麻烦,但用管道命令能绕过,前面给过写法。
5.2 免密失效类问题怎么查
明明配过了,隔几天又开始要密码,这种情况我遇到过好几次,原因基本集中在三类。
第一类是权限被改回去了。有时候是脚本、有时候是手动操作,把.ssh目录或者authorized_keys的权限放宽了,SSH 一检查不通过就回退到密码认证。用ls -ld ~/.ssh和ls -l ~/.ssh/authorized_keys确认权限,不对就 chmod 回来。
第二类是换了电脑或者重装系统,生成了一套新的密钥,但公钥没同步到远程,或者远程authorized_keys里还是旧的那把。这种要靠ssh -v的日志确认,它会告诉你用了哪个私钥文件、公钥是哪个指纹。
第三类是 ssh-agent 里加载了错误的密钥。Windows 的 OpenSSH 如果启用了 agent 服务,可能会优先使用 agent 里的密钥,而你 config 里指定的又是另一个,两者对不上就失败。可以清一下 agent 里的密钥,或者明确指定 IdentityFile。
注意:SSH 的日志非常诚实,几乎所有的认证失败都会在
-v的输出里留下线索。别猜,先看日志。
5.3 让远程体验更顺手的几个设置
连接稳定之后,还有一些小调整能让日常用起来舒服很多。文件保存时的自动上传、终端里的中文显示、远程端的编码格式,这些都是容易被忽略但影响体验的点。
终端中文乱码,通常是远程的 locale 没设置好,或者终端编码不对。可以在 Ubuntu 上确认一下locale的输出,把需要的语言环境装齐,再把默认编码设成 UTF-8。这属于远程环境本身的配置,不是 VS Code 的问题,但会直接影响你写代码时看注释的心情。
再一个是把常用的端口转发配好。如果你在远程跑了个 Web 服务或者数据库,VS Code 能自动检测到端口并提示转发,也在面板里手动加转发规则就行。这样一来,本地浏览器就能直接访问远程服务的页面,调试前端的时候特别方便,不用再去折腾网络配置。
最后说个我自己的习惯:把 ssh config 里的主机别名起得短一点、有意义一点,比如dev、test、prod分开,然后在 VS Code 里按别名连接。时间长了你会发现,这套组合拳打顺了之后,本地和远程的界限会变得很模糊,你只需要关心代码本身,剩下的交给 SSH 和 VS Code 去处理就行了。