ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WSL2 中 SSH 服务自动启动的完整方案:任务计划、脚本与避坑指南

WSL2 中 SSH 服务自动启动的完整方案:任务计划、脚本与避坑指南

如果你平时把 WSL2 当作主力开发环境,一定遇到过这个场景:早上打开电脑,准备 ssh 到 WSL2 里的某个服务,结果终端直接报 Connection refused。查了一圈才发现,WSL2 里的 ssh 服务根本没启动,每次重启 Windows 或者 WSL 实例被关闭后,都得手动敲一遍 service ssh start。这篇就来解决 WSL2 中 ssh 服务的自动启动问题。我用的方法不复杂:写一个启动脚本,交给 Windows 任务计划程序在登录时触发,整个过程实测稳定,也顺带把端口冲突、权限不足这些经典坑讲明白。适合所有想把 WSL2 当正经开发环境、又不想每天早上重复敲命令的人。

1. 为什么 WSL2 里的 ssh 服务总得手动开

1.1 WSL2 不是虚拟机,也不是普通服务

先理解一下 WSL2 的底子。WSL2 基于轻量级虚拟机,但它的生命周期跟 Windows 登录会话绑得很紧:你打开一个 WSL 终端,发行版才被拉起来;Windows 重启、执行 wsl --shutdown,或者一段时间没人用,它就会自动退出。这就导致一个尴尬事实,WSL2 里没有一个像 Windows 服务一样常驻的东西,sshd 这种靠监听端口来工作的进程,一旦 WSL 实例退出就什么都没了。

很多人一开始会困惑:为什么我在传统 Linux 服务器上配好 sshd 就能开机自启,到了 WSL2 里就不行。原因在于 WSL2 没有完整的启动流程,或者说它的“启动流程”是随用随取的。传统发行版里 systemd 会在开机时拉起 ssh.service,但 WSL2 默认可能连 systemd 都没启用,具体要看版本和发行版配置,服务自然没人帮你拉。理解这一点,就不会把问题归结到“ssh 配置不对”上。

还有一个细节容易被忽略:WSL2 的网络是 NAT 转发模式,Windows 通过 localhost 转发到 WSL2 内的端口,这种转发只在 WSL 实例存活时有效。也就是说,哪怕你只是没关终端,但只要 WSL 实例因为某种原因被回收了,localhost 映射也会一起失效。这就解释了为什么“明明刚才还能连,过一会儿就断了”。

1.2 手动启动到底有多烦,以及自动化的收益

手动启动本身不难,麻烦在于“每次都要手动”。特别是你拿 WSL2 当远程开发服务器、或者让 Windows 侧的 IDE 连 WSL2 里的数据库、调试端口时,服务一断,所有依赖它的工具全跟着报错。我之前就经历过一次:远程编辑器连不上、自动化脚本全部失败,排查了半天,最后发现只是一个 service 没开。

把这件事自动化以后,收益很明显:系统登录后 ssh 端口自动就位,不需要任何额外操作;配合密钥登录,整个链路可以做到无感连接。而且这套方案不仅适用于 sshd,数据库、后端开发服务器等需要常驻的进程,都可以用同一套思路挂到系统登录后自启。换句话说,你花一次时间把自动启动配好,后面省掉的是每天无数次重复的 service 命令。

2. 几种自动启动方案,怎么选才不踩坑

2.1 方案对比:任务计划、脚本自启、systemd

常见的做法大致有三种。

第一种是用 Windows 任务计划程序,在登录或开机时执行 wsl 命令,把 WSL 里的 sshd 拉起来。这是最万金油的方式,不依赖 WSL 自身的启动策略,对几乎任何发行版都生效,而且从 Windows 侧发起,执行时机直观可控。

第二种是在 WSL 内部做文章,比如修改 /etc/wsl.conf 里的 boot.command,让 WSL 每次启动时执行一段脚本。这个方法要求发行版没有启用 systemd,否则 boot.command 会被 systemd 取代。它的隐蔽问题是执行时机太早,用户态服务和网络栈可能还没完全就绪,某些依赖网络或环境变量的脚本会莫名失败。

第三种是直接启用 systemd,在 WSL 里把 ssh 服务配置成开机自启。新版 WSL 可以通过在 /etc/wsl.conf 的 [boot] 段写 systemd=true 来开启,开启后就能用 systemctl enable ssh 这样非常标准的 Linux 方式管理。但 systemd 方案对 WSL 版本和发行版都有要求,不是所有环境开箱即用,遇到启动顺序、服务依赖问题时,排查复杂度比前两种高不少。

这三种方案并不是非此即彼的关系。你可以先试试任务计划程序,因为它简单直观,调试方便,而且不改动 WSL 内部的启动策略,出问题回退也容易。systemd 方案虽然最符合 Linux 操作习惯,但我在实际使用中发现,它在 WSL2 上很容易引入“非 Linux 环境里才会出现”的额外复杂性,比如 cgroup 相关告警、服务启动时序错乱,反而增加了学习成本。

2.2 我推荐的组合与理由

我最后采用的方案是:WSL 内部放一个防御性启动脚本,Windows 侧用任务计划程序在登录时触发。这个组合的好处有几点。

脚本负责处理“服务已启动就不要重复启动”的逻辑,避免任务计划重复执行时 service 报错;任务计划程序负责时机选择,登录触发比引导阶段更晚,网络和 Windows 用户环境更完整,不容易出现 sshd 起不来但任务计划还显示成功的情况。

另外,wsl 命令本身支持 -u root 指定用户,任务计划里可以直接以 root 身份执行,省去在 WSL 内配置免密 sudo 的麻烦。如果你已经开启了 systemd,这套手动启动脚本也不会冲突,因为 sshd 本身可以重复启动,加上脚本里有进程检查,就不会出现“操作成功但服务重复重启”的副作用。

3. 完整实操:把 ssh 自动启动做出来

3.1 先把 ssh 服务手动跑通

在搞自动化之前,手动能跑通是前提。先确认发行版里有没有安装 openssh-server,没有就用包管理器装一下。假设你用的是 apt 系的发行版,命令一般是 sudo apt install openssh-server。

装完后编辑 /etc/ssh/sshd_config。需要关注的几个参数我列一下:

参数推荐值原因
Port2222尽量避免默认 22,Windows 侧可能也有 sshd 或安全软件占用 22 端口
PasswordAuthenticationyes首次调试阶段保持开启,稳定后可以改 no 走密钥
PermitRootLoginno默认禁止 root 直接登录更安全,需要时再用普通用户加 sudo
ListenAddress0.0.0.0保证 Windows 的 localhost 转发能到达 sshd

保存后用 sshd -t 检查语法,再执行 service ssh start。然后把 Windows 侧的密钥公钥放到 WSL 用户目录下,路径一般是 ~/.ssh/authorized_keys。没有密钥对就先用 ssh-keygen -t ed25519 生成,公钥文件是 .pub 结尾的那个,内容追加到 authorized_keys 即可。

然后用 ssh -p 2222 user@localhost 试连一下。第一次大概率会遇到 host key 确认,输入 yes 后如果能进到 shell,说明服务本身没问题。这一步千万别跳过,因为后面所有自动化只是把“手动启动”这个过程变成脚本,服务自己跑不通的话,自动化只会放大问题。

3.2 写一个可靠的启动脚本

我习惯在 WSL 内部放一个脚本,路径比如 /usr/local/bin/wsl2-ssh-autostart.sh。脚本内容不长,但有几个关键点值得展开。

#!/bin/sh # 脚本:wsl2-ssh-autostart.sh # 作用:确保 ssh 服务处于运行状态,供 Windows 任务计划程序调用 # 1. 如果 sshd 已经在运行,直接退出 if pgrep -x sshd > /dev/null 2>&1; then exit 0 fi # 2. 优先使用 /etc/init.d/ssh,而不是 service 包装 if [ -x /etc/init.d/ssh ]; then /etc/init.d/ssh start else service ssh start fi # 3. 检查启动结果,输出明确标志方便排查 if pgrep -x sshd > /dev/null 2>&1; then echo "sshd started successfully" else echo "sshd failed to start" >&2 exit 1 fi

这段脚本做了三件事:用 pgrep 做幂等判断,避免任务计划重复触发时“服务已经启动”的报错;优先用 /etc/init.d/ssh 而不是 service,原因是某些环境里 service 命令本身存在,但底层 init 脚本有问题,直接调 init.d 更干净;最后再检查一次进程状态,让任务计划的执行结果有实际意义。

写完记得 chmod +x /usr/local/bin/wsl2-ssh-autostart.sh。

还要提醒一句:如果这个脚本是从 Windows 侧复制进去的,很容易带着 CRLF 换行,Linux 下执行会报错。建议在 WSL 里用 vi 或 nano 重新保存,或者执行 sed -i 's/\r$//' 清理一下。这个问题隐蔽且烦人,具体排查方式我会在后面常见问题里再讲。

3.3 注册 Windows 任务计划程序

脚本准备好之后,重点在 Windows 侧。打开任务计划程序,或者直接用 schtasks 命令。

GUI 方式比较直观:创建任务,触发器选“登录时”;操作选“启动程序”,程序填 C:\Windows\System32\wsl.exe,参数填-d <发行版名称> -u root -e /usr/local/bin/wsl2-ssh-autostart.sh;条件里把“只有在计算机使用交流电源时才启动”取消勾选,避免笔记本拔电后不执行;设置里勾选“如果任务失败,按计划重新启动”。

命令行方式更长但适合复制到文档:

schtasks /create /tn "WSL2-SSH-AutoStart" /tr "wsl.exe -d <发行版名称> -u root -e /usr/local/bin/wsl2-ssh-autostart.sh" /sc onlogon /rl highest /f

这里用 /sc onlogon 表示登录时触发,/rl highest 表示以最高权限运行。如果你希望开机就执行而不是等登录,可以改成 /sc onstart,但 Windows 的 onstart 触发很早,网络和用户环境可能没就绪,容易失败,普通开发场景用 onlogon 更合适。

任务计划的“操作”必须写完整路径和参数。wsl.exe 位于 C:\Windows\System32\wsl.exe,一般不用额外配置 PATH。发行版名称可以通过 wsl -l -q 查看,然后填到 -d 参数里,注意区分大小写,也要留意发行版名可能带空格,需要用引号包住整个参数。

3.4 验证与日常使用建议

配置完先手动运行一次任务计划,看是否正常。在任务计划程序里找到刚建的任务,右键“运行”,过一两秒再看状态。如果显示“正在运行”后变成“成功”,基本就稳了。为了确认 sshd 真的起来了,在 WSL 里执行 service ssh status 或者 pgrep -x sshd。

更完整的验证方式是直接从 Windows 侧发起一次连接,比如ssh -p 2222 user@localhost。能连上说明从任务计划触发、WSL 启动、脚本执行到端口转发整条链路都是通的。

日常使用建议其实就三条:

一是尽量别用默认 22 端口,Windows 自身可能也有 sshd 或其他服务在监听,端口冲突是最常见的问题来源。二是一定要配密钥登录,把公钥放到 WSL 用户的 authorized_keys 里,这样自动化脚本和远程连接都不依赖密码输入。三是每次改完 sshd_config,先在终端手动重启一次服务,确认没问题后再跑任务计划,别把错误状态留给自动化。

4. 常见问题与排查技巧实录

4.1 高频问题速查表

我整理了最常遇到的几个问题,照着查通常很快能定位。

现象可能原因排查方式
ssh 连不上 localhostsshd 没运行WSL 里执行 pgrep -x sshd,为空就手动 service ssh start
提示 Port 22 already in use端口被 Windows 或另一个实例占用netstat -ano 查 22 端口占用,改 sshd_config 端口并重启
任务计划显示“操作成功”但 ssh 不通wsl 命令执行了但脚本内部没真正启动成功手动在终端跑一遍完整 wsl 命令,看脚本输出
登录后任务计划没触发触发器设置错或账户权限不足检查任务计划状态,改为“登录时”,必要时用 /rl highest
password 登录失败sshd_config 没开 PasswordAuthentication检查配置并重启 sshd
公钥登录失败authorized_keys 权限不对执行 chmod 700 ~/.ssh,chmod 600 ~/.ssh/authorized_keys
WSL 里执行脚本报 No such file脚本带了 CRLF 换行执行 sed -i 's/\r$//' 清理脚本

这张表里最容易被忽视的是最后一项。很多人从 Windows 记事本里保存脚本,然后复制到 WSL,换行符变成 \r\n,Linux 解释器把 \r 当成命令的一部分,就会出现明明文件存在却报 No such file 的诡异问题。我印象很深的是,有一次配置任务计划后一直显示失败,最后发现脚本第一行被自动加了个看不见的 \r,排查了一个多小时。

4.2 两个容易忽略但很致命的细节

第一个细节是“用户”。如果任务计划用普通用户触发,脚本里又要以 root 身份执行,那就得靠 wsl -u root。如果发行版里 root 没有设置密码,或者 WSL 默认用户依赖 sudo 且 sudo 需要密码,都会导致自动化失败。所以我的习惯是统一用 -u root 参数,不在任务里依赖 sudo。

第二个细节是任务计划的“起始于”目录。如果创建的批处理或命令没有指定工作目录,某些脚本里用到相对路径就会找不到文件。解决方式也很简单:脚本里所有路径都用绝对路径,不要让任务计划去猜当前目录。

第三个容易踩坑的是防火墙。第一次启动 sshd 后,Windows 可能会弹防火墙授权窗口,如果没弹或者点了取消,局域网内其他机器无法连入,但本机连 localhost 却正常,很容易被误判为配置问题。遇到“本机能连,别人不能连”的情况,第一反应去检查防火墙入站规则,放行对应端口再看。

4.3 后续还可以做的扩展

这套自动启动的思路其实能延展到很多场景。数据库服务、后端进程,只要依赖 WSL2 的启动时机,都可以用同一个任务计划脚本拉起来。比如我自己会把几个常驻服务一起放进一个 autostart 脚本里,Windows 登录后一次拉起,省掉手动开多个服务的流程。

另外,如果某天你发现服务状态不对,可以直接在 Windows 侧执行一次wsl -d <发行版名称> -u root -e /usr/local/bin/wsl2-ssh-autostart.sh,看输出结果,这比开着终端进 WSL 再敲命令要快很多。WSL2 的服务自启坑其实不多,大部分问题都集中在“时机不对”和“环境不对”,而这两个问题,用任务计划程序和绝对路径脚本基本都能绕开。真正让我省心的,还是那句老话:把重复的事交给机器,把机器会出错的地方用防御性脚本兜住。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进