我第一次在Windows上装好Ubuntu WSL,准备配SSH远程登录时,被一连串的报错折腾到怀疑人生。后来把整个过程拆开,才发现几乎所有问题都集中在几个固定环节:sshd没装、服务没起、端口被占、认证没开、权限不对。这篇文章就把我在“第一次ubuntu wsl ssh远程登录”时踩过的坑,以及每一个报错的解决方法,完整记录下来,给同样被折磨的新手一条能直接照做的路线。
内容以实践为主,包含命令和配置,适合刚接触WSL、想通过SSH从Windows终端或局域网其他设备登录Ubuntu环境的朋友。我尽量把每类报错的“为什么”说清楚,这样排查起来才会有方向,而不是每次遇到问题就搜一次贴子。
1. 先搞清楚:WSL里的SSH,服务端和客户端是两个东西
1.1 Ubuntu默认不装sshd,第一步是装openssh-server
很多人和我一样,默认以为WSL里的Ubuntu装完就能ssh登录。实际测试会发现,输入ssh localhost大概率会提示无法连接。原因很简单:Ubuntu这个“最小系统”默认只有SSH客户端,没有服务端。客户端用来连别人,服务端才允许别人连进来。
把这点想明白后,第一次配置就变成了两件事:安装服务端,启动服务端。
sudo apt update sudo apt install openssh-server -y装完先确认状态:
sudo service ssh status如果没启动,就启动并让它保持运行:
sudo service ssh start这里有个新手容易产生误区的地方:在WSL里不需要像真服务器那样配systemctl enable ssh,因为WSL默认用service管理体系,除非你在/etc/wsl.conf里手动开启了systemd支持。传统方式下,每次打开WSL窗口都要手动启动,所以我后面会单独讲怎么开机自启。
1.2 同一台机器上的“两套SSH”,端口冲突的第一个隐患
Windows 10以上版本自带OpenSSH客户端和服务端。这就会形成一个很有意思的局面:你的Windows系统里可能运行着“Windows版sshd”,WSL的Ubuntu里运行着“Ubuntu版sshd”,两者都默认监听22端口。
当你在Windows命令行执行ssh,那是去连接外面;但当你从局域网试图连接这台电脑的22端口时,到底是谁在响应,取决于22端口被哪个服务占用了。如果Windows自己的OpenSSH Server服务在跑,WSL里的sshd连启动都会失败,要么报Address already in use,要么报Bind to port 22 on 0.0.0.0 failed。
检查Windows侧的SSH服务:
Get-Service sshd如果状态是Running,建议直接关掉并设为手动启动,因为它很容易和WSL的SSH抢端口:
Stop-Service sshd Set-Service -Name sshd -StartupType Manual这一步做完,端口冲突的隐患基本去掉。我踩过一次Windows自带sshd和WSL sshd同时存在的坑,最后就是靠这条命令解决。
2. 第一次遇到的高频爆雷:Connection refused
2.1 排查顺序:sshd有没有装、有没有启动、监听哪个地址
连接被拒绝(Connection refused)是所有新手都会碰到的报错,它不代表“密码错误”,而是说明你的连接请求根本没有到达sshd进程。
按这个顺序快速定位:
- 服务是否启动:在WSL里执行
sudo service ssh status - 端口是否被监听:执行
ss -tlnp | grep 22 - 如果显示
sshd监听的是127.0.0.1:22而不是0.0.0.0:22,那基本可以确定是配置问题
看一个正常监听的输出:
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))如果监听地址是127.0.0.1,那外部连接会被直接拒绝。此时打开/etc/ssh/sshd_config,修改监听配置:
sudo nano /etc/ssh/sshd_config找到这几行,保证没有被注释,并把地址设为所有网卡:
Port 22 ListenAddress 0.0.0.0 ListenAddress ::修改后重启sshd:
sudo service ssh restart我之前遇到的就是这个情况,查了一会儿才明白,WSL镜像里默认配置对“外部连接”并不友好,需要手动确认监听地址。
2.2 Windows防火墙与WSL的网络转发关系
WSL2默认采用NAT模式,它的网络结构和真实虚拟机不太一样。Windows会创建一个虚拟交换机,Windows和WSL之间通过这个交换机通信。从Windows侧访问WSL里的服务,经常可以直接通过localhost:22访问,因为Windows会做端口转发。
但从另一台电脑访问这台电脑的WSL服务,则涉及两条链路:先到达Windows的某个端口,再由Windows把流量转到WSL的IP上。如果这一步没设置好,就会出现“在Windows本机连接成功,换台电脑就Connection refused”的诡异现象。
给一个最简单的方案,就是用Windows内置的netsh命令做端口映射。假设WSL的IP是172.20.120.5,在管理员PowerShell里:
netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=172.20.120.5然后把防火墙规则打开:
netsh advfirewall firewall add rule name="WSL SSH" dir=in action=allow protocol=TCP localport=2222另一位电脑就能通过ssh 用户名@这台电脑的IP -p 2222访问。需要提醒的是,WSL2的IP每次重启都可能变,所以端口映射里的connectaddress也要同步更新,很烦但确实是这样。后面我会专门讲怎么用主机名来绕过这个动态IP问题。
2.3 “端口被Windows自己占了”的快速判断
在WSL里启动sshd时报错Bind to port 22 on 0.0.0.0 failed: Permission denied或Address already in use,不要急着改配置文件,先用一条命令看看谁占了22端口。
在Windows PowerShell里:
netstat -ano | findstr :22查看输出里的PID,然后在任务管理器里找到对应进程名。我之前查到的就是sshd.exe,即Windows自带的OpenSSH服务。直接按前文的方法把Windows侧的服务停掉,再回WSL里重启sshd,问题马上消失。
如果不想停Windows的sshd,另一个办法是让WSL的sshd换端口。把/etc/ssh/sshd_config里的Port 22改成Port 2222,然后重启。这同样能解决问题,但后面所有连接命令都要带上-p 2222,对新手来说多记一个参数,出错的概率也会增加。所以我个人优先推荐停掉Windows侧的服务。
3. 能连上但登不进去:认证失败和权限问题
3.1 Permission denied (publickey,password):密码认证没开
连接请求能到达sshd,说明服务本身通了。此时最常见的报错是:
username@192.168.1.10: Permission denied (publickey,password).很多人第一反应是“密码输错了”,但更常见的原因是/etc/ssh/sshd_config里把密码认证关掉了,或者只允许密钥认证。检查以下配置:
PasswordAuthentication yes KbdInteractiveAuthentication yes改完后重启服务:
sudo service ssh restart如果还是不行,检查用户是否存在。WSL登录时用的用户名不一定是Windows用户名,很多用户在WSL里创建了独立账户,或者用默认的root。最容易被忽略的一点是:默认的Ubuntu用户可能不允许root通过SSH登录。如果要用root,需要在sshd_config里显式允许:
PermitRootLogin yes这个不建议长期打开,因为root的SSH访问会给系统增加很大风险面,我只在临时排查时用过。
3.2 authorized_keys文件权限写错后的经典表现
配置密钥登录时,最容易碰到的情况是:公钥内容明明写进去了,但连接时还是不断要求密码,甚至直接拒绝。原因是sshd对文件权限非常敏感,如果~/.ssh或authorized_keys权限太宽松,它会直接丢弃这个文件。
正确的一组权限如下:
mkdir -p ~/.ssh chmod 700 ~/.ssh touch ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys然后编辑authorized_keys,把公钥粘贴进去:
nano ~/.ssh/authorized_keys保存后重启sshd。这一步我建议写进本地笔记里,因为每次新建用户都得重复一遍。顺手还要确认~目录本身的权限不能是777,否则sshd同样会拒绝。
3.3 Host key提示、第一次连接指纹确认
第一次用SSH登录WSL,会看到一段警告:
The authenticity of host '...' can't be established.这是正常的,表示你还没确认过对方的身份。输入yes继续即可。但如果反复出现REMOTE HOST IDENTIFICATION HAS CHANGED,说明WSL重装过或系统重置过,同一IP对应的主机指纹变了,本地的known_hosts还保存着旧记录。
解决办法是删除旧的主机记录:
ssh-keygen -R 目标IP或主机名然后再连接,重新确认指纹。
有一次我遇到“输入yes后卡住不动”,后来发现是因为GSSAPIAuthentication开启,导致登录阶段在尝试Kerberos认证,超时后才回落到普通密码认证。如果连接很慢且响应延迟,可在sshd_config里关掉:
GSSAPIAuthentication no UseDNS no这个细节在初次配置时未必会遇到,但远程登录出现“连接建立后卡很久”的问题,多半与它有关。
4. IP总是变、服务没起来,WSL的网络“体感”问题
4.1 为什么WSL2每次重启IP不一样,怎么固定访问方式
WSL2的动态IP问题让人头疼。每次重启WSL,内部的IP都可能变成另一个值。如果你在Windows里做了端口映射,映射的目标IP也要跟着改,否则连接会失效。
解决思路有两个方向:一是用主机名访问,二是写脚本自动更新映射。
WSL2实际上会给自己设置一个主机名,这个主机名与电脑名相关,在Windows命令行里尝试:
ssh 用户名@主机名有时候能直接解析。若主机名解析不稳定,更可靠的做法是在C:\Windows\System32\drivers\etc\hosts里添加一条固定映射,把WSL当前IP和一个自定义名称绑定。但IP变了之后同样要维护。
我最终选择的是用PowerShell脚本,每次开机自动获取WSL当前IP,然后更新Windows端口映射。脚本大致逻辑如下:
wsl -d Ubuntu -- sh -c "hostname -I" | $wslIp ... netsh interface portproxy delete ... netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=2222 connectaddress=$wslIp connectport=22这个方案适合有一定脚本经验的人。如果不想折腾,就在每次重启WSL后手动查询WSL IP,然后重写一遍映射命令。记住一条原则:WSL2重启了,IP就可能变,端口映射要跟着变。
4.2 让sshd随WSL启动的两种方式
WSL默认不启动systemd,所以不能指望systemctl enable ssh在每次开机时自动生效。常用做法是在用户目录的.bashrc或.profile里加入启动逻辑,这样每次打开WSL终端时都会尝试拉起服务。
个人更推荐用/etc/wsl.conf开启systemd,前提是WSL版本较新。在WSL里编辑:
sudo nano /etc/wsl.conf写入:
[boot] systemd=true然后在Windows PowerShell里重启WSL:
wsl --shutdown再次打开WSL后,systemd会被激活,此时就可以正常使用systemctl enable ssh了。开启systemd后,sshd的管理方式会和Ubuntu服务器一致,可用性和可预期性都更高。
不过要注意,有些使用WSL老版本的人开启systemd后偶尔会遇到启动慢的问题。如果遇到这类情况,退回.bashrc方式启动sshd,也就是在文件末尾加一行:
sudo service ssh start || true哪种方案更好没有绝对答案,取决于你的WSL版本和对systemd的接受程度。
4.3 从局域网其他设备远程WSL的端口映射
从其他设备访问WSL,最直观的方式是在Windows上做端口转发。但我更想提醒的是,这个过程还有两条细节:
第一条,防火墙规则和执行端口映射时,都要以管理员身份运行PowerShell,否则命令被执行时会报权限不足。
第二条,如果Windows本身就可以被局域网访问,那么端口映射后的入口就是“Windows的局域网IP加映射端口”。如果Windows本身的防火墙禁止了对应端口,外部连接依然失败。所以检查顺序是:端口映射是否指向了最新的WSL IP、防火墙是否放行了监听端口、WSL里sshd监听地址是否是0.0.0.0。
我遇到了一个很常见的案例:把端口映射到WSL的IP后,Windows本机连接成功,但手机或另一台电脑连接失败。最终原因是Windows防火墙默认只允许来自本地的连接,外部入站被挡。添加一条允许规则后,外部连接立刻恢复正常。
5. 报错信息对号入座:快速定位表
5.1 常见报错与原因对照速查表
把第一轮踩坑中最高频的报错整理成了下面的表,排查时直接比照,能省不少时间。
| 报错信息 | 问题位置 | 常见原因 |
|---|---|---|
| Connection refused | 服务端端口 | sshd未启动、监听地址错误、端口映射失效 |
| Address already in use | 服务端端口 | Windows sshd或其它程序占用22端口 |
| Permission denied (publickey,password) | 认证配置 | PasswordAuthentication未开启、密码错误 |
| Permission denied (publickey) | 认证配置 | 只允许密钥登录,且公钥信息不匹配 |
| Host key verification failed | 客户端known_hosts | WSL重装或IP被复用,出现指纹冲突 |
| REMOTE HOST IDENTIFICATION HAS CHANGED | 客户端known_hosts | 目标主机指纹变化,需清理旧记录 |
| Connection closed by remote host | 服务端 | sshd配置异常、文件权限异常、启动参数错误 |
| nc: connect to port 22 failed | 客户端 | 端口不通、防火墙拦截、服务未监听 |
Connection closed by remote host这个报错比较麻烦,它不像连接拒绝那样直白。通常要查看WSL里的sshd日志:
sudo tail -n 50 /var/log/auth.log日志里会明确写清楚原因,比如权限不合法、认证失败次数过多。
5.2 一套完整的排查命令顺序(我自己踩坑后固化的)
下面是经过反复使用后确立的排查顺序,推荐新手直接复制到笔记里,遇到问题按顺序跑。
先在WSL侧检查:
sudo service ssh status ss -tlnp | grep 22 tail -n 30 /var/log/auth.log然后切到Windows PowerShell侧检查:
Get-Service sshd netstat -ano | findstr :22 netsh interface portproxy show all注意看端口映射列表,如果里面记录的connectaddress与WSL当前IP不一致,就会产生“能ping通但连不上”的诡异情况。手动更新映射:
netsh interface portproxy set v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=新的WSLIP执行完后再测一次连接。这套顺序能覆盖绝大多数报错,先服务、后端口、再映射、最后看日志。
6. 第一次配置就顺手做好的安全习惯
6.1 别把WSL当生产服务器,但至少要关密码登录
WSL本质上是开发环境,不是面向公网的服务器。如果你的WSL只是开发调试用,通常没有必要把SSH端口暴露到公网。如果为了远程访问方便,不得不暴露到局域网,那么至少不要把密码登录裸奔在网络上。
最直接的安全习惯是:关掉密码登录,只用密钥。
在/etc/ssh/sshd_config中设置:
PasswordAuthentication no ChallengeResponseAuthentication no UsePAM no然后重启sshd。这样即使有人拿到你的IP和用户名,没有私钥也无法进入系统。对于第一次配SSH的新手,这个操作其实顺手就能做掉,不会增加太多配置难度。
有人担心关掉密码登录后,如果私钥丢了就进不去系统。我建议把私钥放在本地加密磁盘中,同时保留一个应急的authorized_keys备份。WSL里如果折腾坏了,也可以通过wsl -d Ubuntu -u root直接进root shell去修。
6.2 配置密钥登录的三分钟内方案
假设你已经有了一对密钥对。如果没有,在Windows上生成:
ssh-keygen -t ed25519 -C "wsl-ssh"生成的公钥在C:\Users\你的用户名\.ssh\id_ed25519.pub。查看后复制内容,回到WSL侧:
mkdir -p ~/.ssh chmod 700 ~/.ssh nano ~/.ssh/authorized_keys粘贴公钥内容后保存,执行:
chmod 600 ~/.ssh/authorized_keys sudo service ssh restart之后从Windows连接时,如果私钥文件名是默认的id_ed25519,直接:
ssh 用户名@localhost -p 22如果私钥在其他路径,指定私钥文件:
ssh -i C:\Users\你的用户名\.ssh\id_ed25519 用户名@localhost -p 22整个流程下来不到三分钟,换来的是不用每次输密码。我个人觉得这件事值得在第一次配SSH时顺手做完,因为后面无论是用VS Code Remote还是各种开发工具,密钥登录都能省掉很多麻烦。
6.3 WSL适合跑什么、不适合跑什么
WSL适合跑软件开发所需的Linux环境,比如编译工具、脚本语言、数据库、Docker(WSL2后端)、微服务调试,也非常适合SSH进去敲命令。不建议的事情包括:长时间暴露公网端口、当作生产业务主机、存重要数据且不备份。
如果确实需要从外部网络访问,不用把WSL的端口直接暴露到公网,更稳妥的方案是把Windows侧的端口转发限定在特定来源IP,或者使用更细粒度的访问控制。WSL每次重启后IP和网络状态都可能变化,这些和普通服务器完全不同的特性,决定了它只适合开发调试,不适合扛流量或跑关键业务。
一个小技巧是定期检查/var/log/auth.log。即使只是局域网里用,日志也会记录每次连接来源。看到奇怪的来源IP时,思考一下是不是端口映射暴露了不该暴露的服务。如果发现自己不需要从外部访问,干脆把Windows侧的portproxy删掉,把默认的22端口访问权留在本地,这是最清净的做法。
最后分享一点个人体会
我第一次把所有报错都跑了一遍之后,最大的感受是:WSL里的SSH本身不复杂,复杂的是它和Windows网络之间的“边界地带”。这个边界地带充满各种默认值、端口占用和转发规则,而系统给我们的提示又往往很有限。遇到问题先不要急着乱改配置,把服务状态、监听端口、防火墙规则这三件事按顺序看一遍,80%的问题都已经浮出水面。
如果你和我一样是第一次接触WSL的SSH,建议先把没有外部访问需求的那套配置做通,把WSL内部和Windows本机之间的连接跑顺,再考虑局域网映射。等密钥登录和端口映射都明白了,再面对“第一次ubuntu wsl ssh远程登录”这一串问题,就会觉得每一步都有迹可循了。