
先说一个我自己的使用场景。有一段时间我需要把笔记本上的一套 Linux 开发环境共享给同局域网里的两三个人用不想直接共享整个桌面于是就在 WSL 里开了 OpenSSH让大家用 SSH 连进来执行脚本。最开始图省事直接装了 Ubuntu但运行一段时间后我明显觉得一个只负责对外开 22 端口的系统没必要占着几百兆内存不放。后来换成 Alpine整个 WSL 发行版连同 OpenSSH 服务加起来常驻内存可以控制在 100MB 上下启动也是秒级。这个组合我沿用至今也就是标题里说的“使用 Alpine 配置 WSL ssh 门户”。这里的“门户”有两层意思一是你的 Linux 环境对外暴露的一道统一入口别人通过 SSH 直接进到 WSL二是它可以当一个跳板机你从客户端 SSH 到这台 WSL 之后再借助它的网络继续访问其他内网机器。下面我就把从选型、安装、SSH 打磨、网络编排到开机自启的完整过程写出来最后补充一些真正踩过的坑。内容不追求花哨但每一条配置都能直接复制使用。1. 为什么偏偏是 Alpine选型背后的资源账1.1 一份轻量级的 SSH 宿主环境Alpine 是一个以轻量出名的 Linux 发行版默认构建基于 musl libc 和 busybox没有 systemd使用 OpenRC 管理服务。对单纯跑 SSH 服务来说这几乎是理想状态系统里没有一堆你用不到的后台进程每一条资源的账都能算清楚。下面是我不久前实测两台 WSL 发行版时的基本印象数字会随具体版本略有浮动但量级差距是真实的维度Ubuntu最小安装Alpineminirootfs根文件系统压缩包数百 MB 级别几十 MB常驻内存仅跑 sshd300MB 以上50~150MB包管理器aptapk初始化系统systemdOpenRC默认 shellbashash可自行安装 bash我特别强调“常驻内存”这个词。WSL 本身是随用随启动的但你如果打算把 SSH 门户当常驻服务用内存占用就不是玄学。很多人的笔记本总共 8GB 内存Windows 开着浏览器和 IDE留给 WSL 的余量本来就不多。Alpine 能把常驻成本压到最低这是我选它的第一个理由。1.2 “门户”到底是做什么的很多人第一次听到“WSL SSH 门户”会以为只是在 Windows 里装个 sshd。实际上它的用途可以更广场景 A把自己的 WSL 环境暴露给局域网设备别人用 SSH 连进来查看代码、执行脚本或运行工具。场景 B把 Alpine WSL 当作跳板机通过 ProxyJump 语法从这台机器再跳到局域网里其他主机。场景 C把 WSL 里的开发工具Git、Python、Docker 等做成可通过 SSH 调用的环境客户端不用重复安装工具链。有人会问Windows 自带 OpenSSH Server为什么不直接用我的体验是Windows 的 OpenSSH 在权限模型、SFTP 根目录、公钥管理这些细节上都不如 Linux 生态顺手。而 WSL 里的 Alpine 就是一台标准 Linux 主机开发团队验证起来零成本后面所有跟 SSH 有关的配置都能复用。既然明确了定位是“门户”那这台系统的职责就非常单一稳定、干净、安全地提供 SSH 服务。Alpine 正好符合这个画像。2. 从装系统到开出 22 端口Alpine WSL 的初始化全流程2.1 安装前先确认 WSL2 在运行做 SSH 门户之前先确认你的 WSL 已经切换到 WSL2 架构。WSL2 基于轻量虚拟机有一份完整 Linux 内核跑 sshd 和 OpenRC 这类服务才像真正的 Linux 主机WSL1 的兼容层跑网络服务容易出各种奇怪问题。在 PowerShell 里检查wsl --status如果返回的默认版本是 1或者你想把某个发行版强制切成 2执行wsl --set-version Alpine 2同时建议保持 Windows 处于较新的版本因为后面我们还会用到 WSL 网络模式的新特性。2.2 两种装 Alpine 的方式我更推荐导入装 Alpine 有两条路径。路径 A从应用商店直接安装名为“Alpine WSL”的发行版。装好后wsl -d Alpine就能进入。这条路对新手最友好缺点是可定制性弱安装位置和文件系统结构不容易控制。路径 B手工导入 Alpine 官方提供的 minirootfs 镜像。这种方式适合离线环境也方便你把整个发行版放到一个指定目录。具体步骤先把目标目录建好比如D:\WSL\Alpine。下载对应架构的alpine-minirootfs-3.x-x86_64.tar.gz。在 PowerShell 里执行导入命令wsl --import Alpine D:\WSL\Alpine D:\Downloads\alpine-minirootfs-3.19.1-x86_64.tar.gz --version 2这里的--version 2明确指定 WSL2。导入完成后直接进入wsl -d Alpine导入版默认以 root 用户登录且没有密码。第一次进去第一件事就是改 root 密码。2.3 进入系统后的第一轮初始化我们按照“普通用户日常使用、root 只负责管理”的思路来做初始配置。先更新软件源apk update然后设置 root 密码并创建一个日常用的用户passwd root adduser -D -s /bin/sh dev passwd devadduser -D是 busybox 风格的参数表示创建一个不自动询问密码的用户所以随后要手动passwd dev。默认 shell 是 ash够用如果你更习惯 bash也可以apk add bash再改 shell。接着创建 SSH 目录并安装 sudo方便 dev 用户临时提权apk add sudo mkdir -p /home/dev/.ssh chown dev:dev /home/dev/.ssh chmod 700 /home/dev/.ssh addgroup dev wheel echo %wheel ALL(ALL:ALL) ALL /etc/sudoersAlpine 默认没有 sudo需要通过 apk 安装。把 dev 加入 wheel 组之后日常维护就不用切到 root 了。接下来是重头戏安装 OpenSSH 服务端并生成主机密钥apk add openssh-server openssh-client ssh-keygen -Assh-keygen -A会自动生成 sshd 需要的各种 host key。如果不做这一步sshd 启动时可能报Could not load host key。生成完之后用 OpenRC 把服务启动起来并加入默认运行级rc-service sshd start rc-update add sshd default检查一下监听状态netstat -tlnp | grep :22如果看到0.0.0.0:22 LISTEN说明 SSH 服务已经成功开出来了。Alpine 不使用systemctl统一用rc-service管理服务这一点先记牢。2.4 创建 wsl.conf 固化几个关键行为在 Alpine 的/etc/wsl.conf里我写了这么几行[user] defaultdev [network] generateResolvConf true [boot] systemdfalsedefaultdev的作用是以后执行wsl -d Alpine时默认进入 dev 用户而不是 root这是最基础的安全习惯。WSL 新版本默认支持 systemd但 Alpine 没装 systemd我明确关掉以免 WSL 尝试做一些多余的系统初始化。generateResolvConf则留给后面的 DNS 问题使用。到这里一台干净、轻量的 WSL Alpine 已经能跑 SSH 了但还远远不够“门户”标准接下来继续打磨。3. SSH 服务端与用户权限的细粒度打磨3.1 先想清楚这台门户要被谁访问、怎么访问我在配生产环境时会先明确一个边界这台机器只给谁用如果只是个人使用需要放行的就只有你自己的公钥如果是几个人协作就把每个人的公钥加进去。无论如何密码登录都应该关闭这是防暴力破解最粗的一条线。3.2 编辑 sshd_config别用默认配置裸奔Alpine 安装 openssh-server 后默认的sshd_config注释很多。我建议直接改成下面这种风格# /etc/ssh/sshd_config Port 22 AddressFamily any ListenAddress 0.0.0.0 PermitRootLogin no MaxAuthTries 3 LoginGraceTime 30 PubkeyAuthentication yes PasswordAuthentication no ChallengeResponseAuthentication no UsePAM no X11Forwarding no AllowUsers dev AllowTcpForwarding yes AllowAgentForwarding yes ClientAliveInterval 60 ClientAliveCountMax 3逐条说明一下关键项PermitRootLogin no禁止 root 直接远程登录。管理需求通过 dev 用户加 sudo 解决。PasswordAuthentication no只接受公钥认证没有公钥的用户一律连不进来。AllowUsers dev白名单只有 dev其他用户即使存在也无法 SSH。AllowTcpForwarding和AllowAgentForwarding这两项是为跳板场景准备的允许你通过这个门户再转发连接。ClientAliveInterval / ClientAliveCountMax让服务端定期探测客户端避免网络假死时留下大量半开连接。改完后重启服务rc-service sshd restart最重要的一点是如果还没把公钥配好就关掉密码登录很容易把自己锁在外面。我的习惯是先允许密码登录和 root 登录把公钥全部灌进去并验证通过之后再改掉这些安全选项。3.3 公钥配置全流程在客户端机器上生成密钥对建议用 ed25519ssh-keygen -t ed25519 -C devlaptop -f ~/.ssh/id_ed25519ed25519 密钥更短、更快安全性也不弱于 RSA。如果你的旧设备不支持才退用ssh-keygen -t rsa -b 4096。把公钥放进 WSL 的authorized_keysssh-copy-id -p 22 devlocalhost如果从局域网其他机器操作就写成ssh-copy-id -p 2222 devwindows_ip。不方便用 ssh-copy-id 时手动追加也可以mkdir -p ~/.ssh chmod 700 ~/.ssh echo ssh-ed25519 AAAA... devlaptop ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys权限必须是 700 和 600。如果目录或文件的权限过宽sshd 会直接拒绝使用这个公钥。Windows 客户端还需要单独注意私钥权限。很多人在 Windows 上用 OpenSSH 时私钥文件权限不对会看到UNPROTECTED PRIVATE KEY FILE的报错。解决办法是在 PowerShell 里执行icacls $env:USERPROFILE\.ssh\id_ed25519 /inheritance:r /grant:r $env:USERNAME:R只给当前用户读取权限然后把继承全部取消。3.4 限制来源 IP可选如果门户只服务于某个网段可以在sshd_config末尾加一段Match Address 192.168.1.0/24 PasswordAuthentication no Match all这段的作用很直接只有来自指定网段的连接才会应用后面的规则。不过我更推荐把来源限制做在 Windows 防火墙那一层因为 sshd_config 只能控制 SSH 本身的策略防火墙可以直接丢包看起来更像一层硬墙。4. 让局域网设备连进来WSL 网络模式与端口转发4.1 WSL2 的默认 NAT 坑WSL2 的虚拟网卡默认是 NAT 模式。也就是说WSL 里的 IP 和 Windows 物理网卡的 IP 不在同一个网络里。你的 sshd 监听0.0.0.0:22监听的是 WSL 内部虚拟网卡Windows 本机可以通过 localhost 转发连到 WSL但局域网里的其他设备访问 Windows 的 IP 时数据包并不会自动进入 WSL。这是新手最容易卡住的地方明明 sshd 起来了、Windows 防火墙也放行了可另一台电脑就是连不上。解决办法有两种先用哪一种取决于你的 Windows 版本和网络环境。4.2 方案 AWSL mirrored 网络模式推荐Win11 22H2如果你的系统版本较新我强烈建议直接用 mirrored 网络模式。它让 WSL 与 Windows 共享同一套网络接口Windows 的物理 IP 同时就是 WSL 的对外 IP。这样一来WSL 里的 sshd 监听0.0.0.0:22后局域网设备直接访问 Windows IP 的 22 端口就能连到 WSL。在用户目录下创建一个.wslconfig文件[wsl2] networkingModemirrored然后让配置生效wsl --shutdown重新启动 WSL。进入 Alpine 后用ip addr或hostname -I看看应该能看到 Windows 网卡同段的 IP。接着在 Windows 防火墙放行入站 22 端口netsh advfirewall firewall add rule nameWSL SSH Inbound dirin actionallow protocolTCP localport22如果只想对局域网开放可以加remoteip192.168.1.0/24这样的参数。我实测下来mirrored 模式在多数家庭和办公网络都非常稳省掉了后面所有改 IP 的麻烦。但它和某些复杂网络代理可能存在兼容性问题如果遇到网络栈异常就退回到方案 B。4.3 方案 BWindows 端口转发 动态更新 IP如果系统是 Windows 10或者 mirrored 模式在你的网络里有问题那就用经典方案Windows 端口转发。先把 WSL 的 22 端口映射到 Windows 的 2222 端口避免和 Windows 本机可能开启的 OpenSSH 服务冲突。先获取一次 WSL IPwsl -d Alpine hostname -I输出可能是多个 IP 空格分隔取第一个即可然后配置转发netsh interface portproxy add v4tov4 listenport2222 listenaddress0.0.0.0 connectport22 connectaddress你的WSL IP由于 WSL2 重启后 IP 会变我写了一个小脚本sync-portproxy.ps1$wslIp (wsl -d Alpine hostname -I).Trim().Split( )[0] if ($wslIp -match ^\d\.\d\.\d\.\d$) { Write-Host WSL IP: $wslIp netsh interface portproxy delete v4tov4 listenport2222 listenaddress0.0.0.0 | Out-Null netsh interface portproxy add v4tov4 listenport2222 listenaddress0.0.0.0 connectport22 connectaddress$wslIp | Out-Null } else { Write-Warning 未能获取有效的 WSL IP }再放行防火墙netsh advfirewall firewall delete rule nameWSL SSH PortProxy netsh advfirewall firewall add rule nameWSL SSH PortProxy dirin actionallow protocolTCP localport2222把脚本挂到 Windows 计划任务里登录时跑一次就可以自动完成端口转发。以后外部设备统一通过ssh -p 2222 devwindows_ip连接。这个方案的缺点是 IP 每次变化都要重新执行脚本。我建议所有能用 mirrored 模式的人都优先用方案 A方案 B 作为降级选择。4.4 验证连通性配置完成后从同局域网的另一台电脑测试ssh -vv -p 2222 devwindows_ip-vv会输出详细握手过程。如果卡在Connection timed out优先查 Windows 防火墙和 portproxy 是否有监听如果报Permission denied基本是公钥或权限问题。不要一上来就怀疑 SSH 服务先按 OSI 模型的顺序排查。5. 开机自启与长期稳定运行让门户不再“人走门关”5.1 Windows 登录时自动拉起 WSLWSL 发行版在系统重启后默认不会自己跑起来所以要用 Windows 计划任务把它拉起。任务触发条件我建议选“用户登录时”因为 WSL 依赖当前用户的网络栈在 SYSTEM 账户下初始化不一定可靠。任务操作里直接调用 wsl.exe参数这样写程序C:\Windows\System32\wsl.exe 参数-d Alpine -u root -e sh -lc /usr/sbin/rc-service sshd start; while true; do sleep 3600; done为什么要加while true因为 WSL2 在没有前台进程时可能被系统回收一个持续运行的循环能保证发行版一直在后台。sshd 服务本身就是常驻进程但保持一个 shell 会话会让任务更稳定。如果你担心重复启动报端口占用可以换成一个更健壮的写法。我在实际部署时用的是批处理文件start-portal.batecho off wsl.exe -d Alpine -u root -e sh -lc if ! /etc/init.d/sshd status; then /etc/init.d/sshd start; fi; exec sleep infinity/etc/init.d/sshd status会返回当前服务状态如果已经在跑就不重复启动最后用exec sleep infinity保持 WSL 存活。把批处理文件路径填进计划任务即可。5.2 睡眠与关机的注意笔记本睡眠再唤醒后网络栈偶尔会重置。Mirrored 模式相对稳定但 NAT 模式下 IP 可能变化导致 portproxy 指向的地址失效。遇到这种情况最直接的检查方法是先在 Windows 上重新获取 WSL IP再更新 portproxy。如果有计划任务挂了同步脚本触发一次即可。如果整个链路都异常执行wsl --shutdown等几秒再重新启动计划任务。注意这会让所有 WSL 发行版停止务必确认没有其他正在跑的 WSL 工作。5.3 为计划任务加一个守护脚本为了让门户更省心我在计划任务里额外加了一个小循环每 5 分钟检查一次 sshd 状态如果发现掉了就拉起。你也可以把 sync-portproxy 脚本也放进循环每几分钟同步一次这样 NAT 模式下 IP 变化后最多迟到几分钟就能自动恢复。不过这些都属于“补救”手段。最好的做法还是先把网络模式切到 mirrored然后用计划任务做开机守护能挡掉 80% 的日常问题。5.4 如果选择 NATportproxy别忘了更新脚本方案 B 有一个隐藏要求netsh interface portproxy里的连接地址在 WSL 重启后会失效所以每次 WSL 重启后都要重新执行同步脚本。我见过很多人在这一步放弃因为他们总忘记跑脚本。可以设置一个开机计划任务专门运行 sync-portproxy也可以在我之前提到的守护循环里同步调用但最省心的仍是 mirrored 模式。6. 用了一段时间后我整理了这些教训和技巧6.1 WSL 里的 DNS 问题Alpine 使用 musl libc对/etc/resolv.conf的处理比 glibc 发行版更敏感。我遇到过一觉醒来WSL 里的/etc/resolv.conf变成空文件SSH 服务本身没挂但包管理器解析不了域名apt 类似的 apk 更新也因此失败。这时先确认/etc/wsl.conf里[network] generateResolvConf true存在然后在 Windows 端重启发行版wsl --terminate AlpineWSL 会重新生成 resolv.conf。如果网络环境本身有问题再手动在 Alpine 里临时写入一个可用 DNS 作为兜底但要记住这不是长期方案。6.2 千万别做这些事以下几条是我和身边朋友踩过的坑整理成清单别用密码登录暴露在可信网络之外。只要这台机器能被外部访问密码认证就是风险源。别默认 root 登录。日常都用 dev 用户需要权限时走 sudo。别在 Windows 上同时开一个监听 22 的 OpenSSH Server又给 WSL 转发 22 端口很容易端口冲突。别忘记升级。Alpine 的安全补丁更新很及时定时执行apk upgrade不会浪费多少时间。别反复wsl --shutdown。每次关闭都会把门户断开如果有人正在远程连接可能直接掉线。6.3 作为跳板机时的高级用法如果你想把门户真正当跳板用客户端配置可以写得很简洁。在~/.ssh/config里添加Host portal HostName 192.168.1.100 Port 2222 User dev IdentityFile ~/.ssh/id_ed25519 ForwardAgent yes Host internal HostName 10.0.2.5 User server ProxyJump portal之后你只需要执行ssh internal它会先连接到 WSL 门户再通过门户的网络跳到10.0.2.5。这个用法让我在局域网里维护多台机器时不必把每一个目标主机的密钥都复制到本机只要门户上的公钥可控就够了。6.4 最后的稳定形态这套配置最终稳定运行后的形态是发行版Alpine根文件系统很小常驻内存低。SSHOpenSSH Server监听 22仅密钥登录只允许 dev 用户。网络mirrored 模式Windows 防火墙只放行局域网的 22 端口。启动计划任务在登录后自动拉起 WSL并通过 sleep 保持运行。升级每隔一段时间进 WSL 执行apk update apk upgrade。折腾完这些你会忘记它的存在。直到某天你在另一台电脑上熟练地敲下ssh portal并进入你自己的开发环境时才会体会到这个“轻量门槛”到底值多少钱。如果你也想给 Windows 配一个不占资源、不打扰日常的 SSH 入口直接照着这条路线动手就行。Alpine 的极简风格一开始可能让你不适应但一旦把它调成你需要的样子它就会安安静静地待在后台成为你局域网里最不起眼也最靠谱的一扇门。