☰
Windows Server 配置 OpenSSH:公钥登录与故障排查
2026/10/1 19:17:06 网站建设 项目流程

凌晨两点,机房那台 Windows Server 的应用服务卡死了,远程桌面怎么点都连不上,你只能干等第二天早上有人进机房按电源键——这种场景在运维圈子里太常见了。远程桌面(RDP)这条路一旦被网络策略挡掉、被会话数占满、或者被系统更新搞挂,手里没有第二条通道的时候,人就特别被动。OpenSSH装到 Windows 上,本质上就是给自己留一条稳定、轻量、可脚本化的第二条命:命令行能进、文件能传、命令能编排,还不占图形界面的开销。这篇内容我打算把 Windows 上安装 OpenSSH 这件事从头到尾捋一遍,从系统版本能不能装、在线和离线两条路怎么走、服务端配置里哪几行非改不可,到公钥登录那两个能把人折腾半天的权限坑,都写清楚。不管你是刚接触命令行的新手,还是天天跟服务器打交道的运维,照着走都能落地。

1. 先弄清楚 Windows 上的 OpenSSH 到底是两个什么东西

1.1 客户端和服务端,装错了等于白干

很多人一上来就搜"Windows 安装 OpenSSH",然后照着第一条结果敲命令,装完发现ssh命令能用了,就以为大功告成——但你真正想干的是"让别的机器连进来",那要装的是服务端,不是客户端。这两个是完全独立的组件,在 Windows 里分别叫OpenSSH Client和OpenSSH Server,可以单独装,也可以一起装。

用最直白的话区分:客户端就是"我主动去连别人",服务端就是"别人来连我"。你笔记本上要连公司服务器,装客户端就够了;那台放在机房的 Windows Server 要接受从你笔记本发来的连接,就得装服务端。同一台机器两个都装也完全没问题,互不冲突。

顺便说一句,客户端其实大多数情况下根本不用装。Windows 10 1809 之后的版本、Windows 11 全系,默认就带客户端了。你打开 PowerShell 敲ssh -V,如果回显类似OpenSSH_for_Windows_9.x,说明已经有了。真没必要为了一个已经存在的东西去重复安装一遍。

1.2 微软维护的 Windows 版和 Linux 上的同源不同形

Windows 上这套 OpenSSH 来自 OpenBSD 项目,由微软的 PowerShell 团队移植维护(Win32-OpenSSH),代码同源,但运行形态差别不小。Linux 上 sshd 靠/etc/ssh/sshd_config和一堆 PAM 模块,用户认证走系统账户体系;Windows 上 sshd 是一个标准 Windows 服务,认证走的是本地账户或者域账户,配置文件落在C:\ProgramData\ssh\sshd_config,日志写进事件查看器而不是/var/log/secure。

这个差异带来的最直接影响是:你在 Linux 上背下来的那套排错经验,在 Windows 上有一半不适用。比如 Linux 上看日志靠tail -f /var/log/secure,Windows 上得用Get-WinEvent去读OpenSSH/Operational这个事件日志通道。再比如 Linux 上文件和目录的权限是 POSIX 位,Windows 上是 ACL,所以那个经典的"authorized_keys 权限太开放导致拒绝登录"的问题,在 Windows 上表现成"ACL 里有不该有的写权限",修法完全不一样。

配置文件本身倒是几乎一致,Port、PasswordAuthentication、PubkeyAuthentication、Subsystem这些关键字两边通用,会 Linux 的话上手很快,这点很省心。

1.3 什么情况下值得在 Windows 上开 sshd

不是所有 Windows 机器都需要装服务端。我自己的判断标准是三条:

第一,这台机器需要被自动化工具碰。比如 CI/CD 流水线要把构建产物推到 Windows 服务器上,或者 Ansible、Jenkins 这类工具要批量执行命令。这些工具绝大多数原生就走 SSH,走 WinRM 也能干但配置更啰嗦,SSH 是阻力最小的路。

第二,远程桌面不够用或者不可靠。RDP 会话数有限制(普通版本同时只允许一个交互会话),网络抖动一下画面就卡死,带宽占用也大。SSH 一个连接几十 KB 内存,掉线重连几乎无感,拿来跑长时间任务舒服得多。

第三,需要一条备份通道。这跟第一点不冲突——我遇到过好几次防火墙策略变更把 3389 封了,或者 RDP 服务因为某个更新起不来,这时候如果机器上有个 22 端口开着,人能立刻进去把问题修了,而不是等现场支持。

反过来,如果这台机器就在你手边、只有一个用户用、也没有任何自动化需求,那装服务端属于给自己增加攻击面,没必要。

2. 动手之前,这几项前置检查别跳过

2.1 系统版本直接决定你能走哪条安装路

这点很多人忽略,直接导致了后面一堆"为什么我的设置里没有 OpenSSH 这个选项"。把版本对应关系说清楚:

系统版本客户端服务端推荐安装方式
Windows 10 1809 及以上内置可选功能可装设置面板或 PowerShell
Windows 11 全系内置可选功能可装设置面板或 PowerShell
Windows Server 2019内置后期版本才有PowerShell 加可选功能
Windows Server 2022 / 2025内置可选功能可装设置面板或 PowerShell
Windows Server 2016 及更早无无手工部署官方发布包
Windows 7 / 8.1 / Embedded无无手工部署官方发布包

Server 2016 是个明显的分水岭。我手上还有几台跑着 2016 的老机器,Get-WindowsCapability -Online -Name OpenSSH*敲下去什么都返回不了,因为那个年代压根没有这个能力包。这种情况只能走离线手工部署那条路,后面第 4 节会详细讲。

2.2 端口、账户、权限三件事先探一遍

安装之前花两分钟做三个检查,能省掉后面一小时的困惑。

第一个是端口占用。Windows 上有时候会有别的程序抢先占了 22 端口,比如某些第三方 SSH 实现、某些备份软件、甚至杀毒软件的组件。检查命令是:

Get-NetTCPConnection -LocalPort 22 -ErrorAction SilentlyContinue Get-NetTCPConnection -LocalPort 22 -State Listen -ErrorAction SilentlyContinue

如果返回了东西,看清楚OwningProcess那一列,再Get-Process -Id <PID>查是什么程序。真有占用,要么把那个程序挪走,要么把自己的 sshd 换个端口。

第二个是账户。Windows 上 sshd 默认允许所有本地账户登录,但管理员组(Administrators)成员和非管理员成员走的是两套完全不同的公钥文件路径,这是后面最容易踩的坑,先记住这个前提。另外要确认你打算用来登录的账户是不是有密码——空密码账户默认是登不上的。

第三个是权限。安装 OpenSSH 服务端、启停服务、改注册表,都需要管理员权限的 PowerShell。普通 PowerShell 窗口敲Add-WindowsCapability会直接报拒绝访问,这个报错信息有时候还挺不明显的,容易让人以为是系统版本问题。

2.3 四种安装方式横向对比

到底用哪种方式装,取决于你的网络环境和运维习惯。我把常见四条路的差异列一下,方便对号入座:

安装方式需要联网版本新鲜度适合场景主要缺点
设置面板可选功能是跟随系统更新单机、图形界面可用无法批量、版本偏旧
PowerShell/DISM 命令是跟随系统更新批量部署、脚本化老系统不支持
官方 MSI 安装包否新离线环境、内网批量需自行维护升级
官方 zip 绿色包否新老系统、便携部署、想手动控制要跑初始化脚本,步骤多

我的习惯是:能给新系统走可选功能就走可选功能,省事且跟着 Windows 更新自动打补丁;只有老系统或者明确要求离线时才动 MSI 和 zip 包。下面分两节把在线和离线两条路都走一遍。

3. 在线安装:设置面板和命令行两种走法

3.1 用设置面板点几下装完

这条路适合单机操作、有图形界面的场景,全程不需要记命令。路径是:设置 → 应用 → 可选功能 → 点击"添加功能"旁边的"查看功能"。

在搜索框里输入OpenSSH,正常情况下会看到两个条目:OpenSSH 客户端和OpenSSH 服务器。客户端一般显示已安装,服务端显示未安装。勾选你要的那个,点"下一步",再点"安装",剩下的就等系统自己去 Windows 更新服务器拉包。

这里有个小细节值得说一下:安装过程实际上是在下载,如果 Windows Update 服务被组策略禁用、或者更新源不可达,这一步会卡很久然后报错 0x800f0954 之类的错误码。这不是 OpenSSH 的问题,是系统更新通道的问题。解决办法一般是用命令行走 DISM,指定本地源或者走 WSUS,或者干脆换离线包。我遇到过内网机器整片都卡在这一步,最后统一改成 MSI 离线部署,反而更干脆。

安装完不需要重启,但服务端装完之后服务默认是"手动"启动状态的,不会自己跑起来,这点一定要注意。下一步要去把服务启动起来。

3.2 PowerShell 命令行的装法和回显解读

批量部署或者习惯命令行的话,就这么几条命令。先查状态:

Get-WindowsCapability -Online -Name OpenSSH*

正常返回长这样:

Name : OpenSSH.Client~~~~0.0.1.0 State : Installed Name : OpenSSH.Server~~~~0.0.1.0 State : NotPresent

State只有两个值:Installed和NotPresent。看到NotPresent就是没装。装服务端:

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

那条~~~~0.0.1.0的尾巴不能省,它是能力包版本标识,少了会提示找不到包。装客户端就是把名字换成OpenSSH.Client~~~~0.0.1.0。

如果 PowerShell 这条路因为系统组件损坏报错,可以退回 DISM 试试,成功率往往更高:

DISM /Online /Add-Capability /CapabilityName:OpenSSH.Server~~~~0.0.1.0

DISM 的好处是错误信息更具体,比如它会明确告诉你"源文件找不到"还是"组件存储损坏",方便定位。实在修不动就DISM /Online /Cleanup-Image /RestoreHealth先修组件存储,再回来装。

3.3 装完必做的三行验证

装完之后别急着开防火墙,先确认三件事,能少走很多弯路。

第一行,确认命令在不在:

Get-Command ssh -All Get-Command sshd -All

服务端装完之后,sshd.exe会被放到C:\Windows\System32\OpenSSH\下,这个目录默认就在系统 PATH 里。注意我特意用了-All参数,因为很多时候你机器上不止一个ssh.exe——Git for Windows 会带一个、WSL 里有一个、可能还有 msys2 的。-All会把所有能匹配到的列出来,顺序就是 PATH 的搜索顺序。这个多份 ssh 的问题后面第 7 节专门讲。

第二行,确认服务在不在:

Get-Service sshd

正常能看到Status : Stopped、StartType : Manual。装完就是这个状态,没启动是正常的。

第三行,确认防火墙规则有没有被自动创建:

Get-NetFirewallRule -Name *ssh* | Select-Object Name, DisplayName, Enabled, Direction, Action

可选功能方式安装时,通常会顺带创建一条叫OpenSSH-Server-In-TCP的入站规则,允许 TCP 22 端口进来。如果这条规则不存在或者被禁用了,外面是连不上的,这个检查能帮你排除一半的"连不上"问题。

4. 离线部署:MSI 包和绿色包的完整走法

4.1 离线包从哪来,怎么校验

内网机器、隔离环境、或者 Server 2016 这类老系统,都得走离线。安装包来源是微软 PowerShell 团队在公共代码托管平台维护的 Win32-OpenSSH 发布页,那里有 MSI 和 zip 两种产物。

下载的时候有个选择:MSI 适合批量、适合走组策略分发、适合交给不熟命令行的人;zip 绿色包适合想手动控制安装位置、或者需要在老系统上折腾的场景。文件命名大概是OpenSSH-Win64-vX.X.X.X.msi和OpenSSH-Win64.zip这种形式,注意选Win64而不是Win32,除非你确实在跑 32 位系统。

下载完之后,一定要核对哈希值。发布页上一般会给出 SHA256,本地算一遍对比:

Get-FileHash .\OpenSSH-Win64-vX.X.X.X.msi -Algorithm SHA256

这一步看着多余,但在内网环境里,安装包经常是经由好几台机器接力拷贝进来的,中间出错的概率比你想的高。我见过一次拷了一半的 MSI,装的时候报了个特别莫名其妙的错误,排查了半天才发现是包本身不完整。

4.2 MSI 的静默安装和参数说明

MSI 的图形安装就是下一步下一步,没什么好说的。批量场景下用静默安装:

msiexec /i OpenSSH-Win64-vX.X.X.X.msi /quiet /norestart msiexec /i OpenSSH-Win64-vX.X.X.X.msi /quiet /norestart ADDLOCAL=Server,Client

第二个命令里的ADDLOCAL参数是选择性安装的核心。MSI 里的功能组件一般有三种:Server(服务端)、Client(客户端)、ClientAndServer。默认行为有时候只装客户端,所以批量部署时最好显式指定,免得装完一半机器发现没有 sshd。

/quiet是静默不弹窗,/norestart是别自动重启。这两个参数组合起来适合放进批处理或者配置管理工具里跑。

装完之后 MSI 会自动注册服务,但同样地,服务依然是手动启动状态。另外 MSI 安装也会自动创建防火墙规则,一般不用你操心。

4.3 zip 绿色包:手工初始化的关键几步

老系统上 MSI 有时候会因为系统组件版本不够而失败,这时候 zip 绿色包是更稳的选择。步骤稍微多一点,但每一步都能控制。

先把压缩包解到一个固定目录,强烈建议放在C:\Program Files\OpenSSH\,不要放在用户目录下、更不要放在桌面上。原因很实际:sshd 服务是以一个虚拟账户(NT SERVICE\sshd)运行的,它需要读取这个目录下的可执行文件和配置文件。如果你把它解在某个用户的家目录里,那个目录的 ACL 可能把这个虚拟账户挡在外面,服务启动时会因为读不到文件而失败,报错信息还不直观。

解压完之后,用管理员权限的 PowerShell 进到那个目录,跑初始化脚本:

cd "C:\Program Files\OpenSSH" powershell.exe -ExecutionPolicy Bypass -File .\install-sshd.ps1

-ExecutionPolicy Bypass这个参数是必须的,因为默认执行策略通常不允许跑未签名的脚本,不加会直接被拦。

脚本跑完之后,会做三件事:注册sshd和ssh-agent两个服务、设置服务启动类型、根据情况生成主机密钥。如果它提示主机密钥生成失败,或者你手工把C:\ProgramData\ssh下的密钥删了,可以手工补:

cd "C:\Program Files\OpenSSH" .\ssh-keygen.exe -t ed25519 -f C:\ProgramData\ssh\ssh_host_ed25519_key .\ssh-keygen.exe -t rsa -b 3072 -f C:\ProgramData\ssh\ssh_host_rsa_key

主机密钥是服务端的身份凭证,客户端第一次连接时看到的那个指纹就是它。这几对密钥不能随便换,一换所有客户端都会报"主机密钥已改变"的警告,需要重新确认。所以生成之后就当作长期资产对待吧。

4.4 老系统上的额外注意点

Server 2016 这类老系统还有个坑:默认的加密算法套件可能不满足新版 OpenSSH 的要求。具体表现是服务能起来,但客户端连接时握手失败,报no matching key exchange method found或者no matching host key type found之类的错误。根因是系统的加密库版本比较老,缺少一些新算法实现。

临时绕过的办法是在客户端侧指定算法:

ssh -o KexAlgorithms=diffie-hellman-group14-sha256 -o HostKeyAlgorithms=+ssh-rsa user@host

但这只是绕,长期方案还是给系统打上最新的补丁。我这里要提醒一句,不要为了连一台老机器就把客户端的加密策略整体降级到最松,那等于给所有连接都开了口子。用-o参数针对单次连接做临时放宽,或者写进~/.ssh/config里针对那一台主机做例外,才是正确的做法。

5. 服务端配置:让 Windows 真正能被 SSH 连上

5.1 sshd_config 里真正需要动的那几行

配置文件在C:\ProgramData\ssh\sshd_config。注意是ProgramData不是Program Files,这两个名字长得像,我见过不止一个人改错了文件,改了半天没生效还以为配置不生效。

打开之后你会发现大部分行都是注释掉的,注释状态意味着使用默认值。真正常改的就这么几行:

Port 22 PasswordAuthentication yes PubkeyAuthentication yes Subsystem sftp sftp-server.exe Match Group administrators AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys

逐条解释一下。Port改端口用,默认 22。PasswordAuthentication控制是否允许密码登录,上线初期一定要保持 yes,等公钥登录验证通过了再考虑关掉,否则很容易把自己锁在外面。PubkeyAuthentication控制公钥登录,默认开。

Subsystem sftp这行是 SFTP 功能的基础,Windows 版默认就开了,指向sftp-server.exe。这行没了 SFTP 就用不了,所以尽量不要删。

最后那个Match Group administrators块是 Windows 版独有的东西,也是最大的一个坑:任何属于管理员组的账户,它的授权公钥文件路径会被强制指向C:\ProgramData\ssh\administrators_authorized_keys,而不是用户家目录下的.ssh\authorized_keys。这个块在文件末尾,位置很显眼,但很多人压根不知道它是干嘛的。

5.2 服务启动、开机自启、防火墙放行三件套

配置改完之后,按顺序做这三件事。启动服务:

Start-Service sshd

设置成开机自启:

Set-Service -Name sshd -StartupType Automatic

可选功能的安装脚本通常已经帮你设成自动了,但手工部署的场景下需要自己设。服务端最怕的就是机器重启之后人连不上,还得等现场支持,所以自动启动这个设置别省。

防火墙这块,可选功能安装一般会创建规则,手工部署的得自己加:

New-NetFirewallRule -Name "OpenSSH-Server-In-TCP" ` -DisplayName "OpenSSH Server (sshd)" ` -Enabled True -Direction Inbound -Protocol TCP ` -Action Allow -LocalPort 22

如果你改了端口,这里的-LocalPort也要跟着改。另外默认情况下这个规则是应用到所有网络配置文件的(域、专用、公用),如果机器直接暴露在不可信网络里,可以加-Profile Domain,Private来限制只在内网生效。

改完配置之后要重启服务才生效:

Restart-Service sshd

这里有个操作细节,重启 sshd 会把你当前正在使用的 SSH 连接一起断掉。所以远程改配置的时候,最好留一个 RDP 会话或者第二个 SSH 连接在手边,万一改错了还能进去捞人。这是我在生产环境里养成的习惯,被坑过之后特别在意。

5.3 默认 Shell 换成 PowerShell

Windows 版 sshd 默认给你的 Shell 是cmd.exe。这东西能用,但体验很差:没有管道操作符的高级用法、没有对象输出、Tab 补全弱、中文编码还容易出问题。换掉它只需要改一个注册表值:

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" ` -Name DefaultShell ` -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" ` -PropertyType String -Force

如果你装了 PowerShell 7,路径换成C:\Program Files\PowerShell\7\pwsh.exe体验更好,启动更快、跨平台兼容性也强。但要注意一点:PowerShell 7 的安装路径会随着大版本变化,从 6 到 7 到 7.x 每一个大版本的目录名可能不一样,升级之后这个注册表值会失效指向一个不存在的文件,然后 SSH 登录就会直接断开。升级完 PowerShell 记得回来检查这一项。

还有个说法是"这个注册表值只对密码登录生效,公钥登录不生效",这个说法我实测下来在较新版本上是过时的,现在两种登录方式都会读到这个值。如果你确实遇到了公钥登录跑到 cmd 里的情况,检查一下是不是账户的 Shell 被单独指定了,或者配置里有别的地方覆盖了这个默认值。

5.4 公钥登录:两个能把人逼疯的权限坑

密码登录能用了,但真要在生产环境用起来,还是得上公钥。Windows 上的公钥登录有两个坑,跟 Linux 的逻辑不一样。

第一个坑是管理员组用户的密钥路径。前面提到的那个Match Group administrators块,效果就是:只要你的账户在 Administrators 组里,sshd 只会去读C:\ProgramData\ssh\administrators_authorized_keys,完全无视你放在C:\Users\<用户名>\.ssh\authorized_keys里的东西。很多人的排查过程是这样的:生成密钥、上传公钥、改权限、重启服务、连接、还是提示输密码,然后反复检查自己那个authorized_keys文件,怎么都找不出问题——因为它压根没被读过。

解决办法就是把公钥内容追加到那个全局文件里:

$key = Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" Add-Content -Path "C:\ProgramData\ssh\administrators_authorized_keys" -Value $key

第二个坑是这个文件的 ACL。Windows 版 sshd 对administrators_authorized_keys的权限检查非常严格,要求它只能被 Administrators 和 SYSTEM 两个主体访问,且不能有继承来的权限。文件里多出任何一个带写权限的主体,sshd 会直接忽略这个文件,并且在默认日志级别下什么都不说,就让你在那猜。用这个命令把它修干净:

icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r ` /grant "Administrators:F" /grant "SYSTEM:F"

对非管理员用户的普通authorized_keys,要求是只有该用户和 SYSTEM 可以访问:

icacls.exe "$env:USERPROFILE\.ssh\authorized_keys" /inheritance:r ` /grant "$env:USERNAME:F" /grant "SYSTEM:F"

如果你已经装了 OpenSSHUtils 这个 PowerShell 模块,可以偷懒让它自动修:

Install-Module -Force OpenSSHUtils -Scope AllUsers Repair-AuthorizedKeyPermission -FilePath "C:\ProgramData\ssh\administrators_authorized_keys"

我个人的经验是:遇到公钥登录失败,第一件事就是在客户端加-vvv参数看详细日志。日志里会明确写出来Authentication refused: bad ownership or modes for file或者类似的提示,看一眼就知道是权限问题还是路径问题,比盲目试强得多。

ssh -vvv user@host

5.5 SFTP 和文件传输的那些细节

SFTP 子系统默认是开的,连上就能用:

sftp user@host

进去之后put、get、ls、cd、lcd(切换本地目录)这些命令跟 Linux 上一样。有个细节是 Windows 路径里的反斜杠在 sftp 客户端里要写成正斜杠,或者加引号,否则会被当成转义字符处理。

上传中文名文件的时候可能会遇到编码问题,表现是文件名显示成乱码或者上传失败。这个跟客户端的 locale 设置和 Windows 文件系统的 Unicode 处理有关,稳妥做法是尽量避免在传输用的文件名里使用中文和特殊字符,用英文加下划线,这能规避掉绝大多数莫名其妙的传输失败。

另外,Windows 上还有一个习惯上的差异值得提一句:Windows 管理员做文件操作用的工具通常是资源管理器或者 WinSCP,很少直接敲 sftp。这没问题,WinSCP 底层也是走 SFTP 协议,跟 sshd 的对接没有任何区别。只要你的服务端配置对了,图形工具和命令行工具都能连上。

6. 客户端实战:用 Windows 自带的 ssh 干活

6.1 首次连接的指纹确认和 known_hosts

第一次连一台新机器,会看到类似这样的提示:

The authenticity of host 'x.x.x.x (x.x.x.x)' can't be established. ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxx. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])?

这是让你确认对方的主机密钥。在生产环境里,这个指纹应该跟服务端管理员核对一下再输 yes,尤其是有安全要求的场景。指纹可以在服务端用这条命令打印出来:

ssh-keygen -lf C:\ProgramData\ssh\ssh_host_ed25519_key.pub

确认之后,指纹会被写进C:\Users\<用户名>\.ssh\known_hosts。下次连接就不会再问。如果哪天服务端重装了或者换了主机密钥,你会看到那个吓人的大红警告"REMOTE HOST IDENTIFICATION HAS CHANGED",这时候要去known_hosts里把对应的那一行删掉,确认是合法的变更之后再重连。千万不要养成看到警告直接删文件重连的习惯,那个警告本身是有意义的。

6.2 把命令缩短:config 文件的几个实用写法

C:\Users\<用户名>\.ssh\config这个文件不存在的话可以自己建,它是纯文本,格式固定。有了它,那些又长又难记的连接参数就不用每次敲了:

Host dev-box HostName 192.168.1.50 User deploy Port 2222 IdentityFile C:\Users\me\.ssh\id_ed25519 Host * ServerAliveInterval 60 ServerAliveCountMax 3 TCPKeepAlive yes

配好之后,一条ssh dev-box就够了,端口、用户名、私钥路径全都自动带上。

第二段那个Host *是所有主机的通配配置,里面那三行是我强烈建议加上的。ServerAliveInterval的作用是每 60 秒向服务端发一个探测包,防止因为中间有防火墙或者 NAT 设备把空闲连接掐掉。默认情况下一个空闲的 SSH 连接可能几分钟后就被静默断开,表现为你切出去看了会儿网页回来发现终端死了。加上这三行之后,连接能稳住几个小时不断。

想确认某个主机最终生效的配置到底是什么,用这个命令:

ssh -G dev-box

它会把所有生效的配置项全部打印出来,包括默认值。排查"我明明在 config 里写了为什么不生效"这类问题时特别好用——十有八九是因为匹配顺序问题,config 里第一个匹配到的值生效,后面的会被忽略。

6.3 密钥生成、ssh-agent 和免密登录

生成密钥:

ssh-keygen -t ed25519 -C "me@workstation"

算法选 ed25519,这是目前的主流推荐,密钥短、速度快、安全性够。RSA 也能用,如果非要 RSA 建议至少 3072 位:

ssh-keygen -t rsa -b 3072 -C "me@workstation"

生成过程中问你要不要设置 passphrase。我的建议是设,尤其是笔记本这种可能丢的设备。设了之后私钥被偷走也用不了,会多一层保护。代价是每次用都要输一遍,这就要靠 ssh-agent 来解决了。

Windows 上的 ssh-agent 也是一个服务,默认可能是禁用状态。启用它:

Set-Service -Name ssh-agent -StartupType Automatic Start-Service ssh-agent ssh-add $env:USERPROFILE\.ssh\id_ed25519

加进去之后,这次开机期间再连就不需要输 passphrase 了。注意ssh-add加进去的密钥在服务重启之后就没了,所以如果你经常重启机器,可能会觉得每次开机都要重新 add 很烦。这个是正常的,从安全角度讲也是合理的——不加持久化,密钥只在内存里。

7. 常见故障排查:这几个场景我踩过不止一次

7.1 服务起不来,报错五花八门

服务启动失败是最常见的开局问题,几种典型情况:

报"错误 1067:进程意外终止",十有八九是配置文件语法错了。sshd 对配置文件格式很敏感,多一个空格、少一个引号都会导致解析失败。诊断方法是用调试模式手工跑一遍:

cd "C:\Program Files\OpenSSH" .\sshd.exe -ddd -f C:\ProgramData\ssh\sshd_config

-ddd是最高级别的调试输出,它会把配置文件解析过程、主机密钥加载过程、绑定端口过程全都打出来,哪一步出错一眼就能看到。注意这么跑的时候是前台运行,会占住终端,测试完 Ctrl+C 停掉就行。不要在服务已经跑着的时候再这么跑,会因为端口被占用而失败,那是正常的。

报"no hostkeys available -- exiting",说明主机密钥丢了或者读不到。检查C:\ProgramData\ssh\下有没有ssh_host_*文件。没有的话手工生成(命令见 4.3 节)。有文件但读不到,就是权限问题,用 OpenSSHUtils 里的Repair-SshdHostKeyPermission修一下。

服务能起但马上就退,看事件日志是最快的路径:

Get-WinEvent -LogName 'OpenSSH/Operational' -MaxEvents 20 | Format-Table TimeCreated, Id, LevelDisplayName, Message -AutoSize

这个日志通道在服务端装好之后就是开启的,里面记录了连接、认证、会话的所有关键事件。认证失败的记录尤其有用,它会明确告诉你失败原因是密码错、密钥被拒、还是权限问题。

7.2 能连上但登录被拒的几种情况

连上 TCP 但登录被拒,问题就集中在认证环节了。按这个顺序排查:

先确认账户名对不对。Windows 的账户名有时候带域名或者机器名前缀,格式是机器名\用户名或者域\用户名。用whoami在目标机上确认真实格式,SSH 里就照那个写。

再确认密码。这个不用多说,但要注意空密码账户默认是登不上的,即使你在配置里开了密码认证也一样。

然后就是前面反复提到的公钥路径和权限问题。这里给一个速查表:

现象最可能的原因处理方式
一直提示输密码,公钥无效管理员组用户密钥放错位置追加到administrators_authorized_keys
密钥在正确位置但仍被拒ACL 里有多余的写权限主体用 icacls 重置继承并只授权 Administrators 和 SYSTEM
公钥登录直接断开家目录或 .ssh 目录权限过松检查目录 ACL,移除多余主体
提示 too many authentication failures客户端一次性尝试了太多密钥在 config 里加IdentitiesOnly yes并指定 IdentityFile
密码正确但提示拒绝访问账户被禁用或不在允许列表net user <用户名>查看账户状态

IdentitiesOnly yes这一条值得单独说。ssh-agent 里如果攒了七八个密钥,客户端会把它们一个一个试过去,很多服务端限制最多 6 次认证尝试,还没轮到正确的那把就被踢了。加上这个配置强制只用指定的那把,问题就消失了。

7.3 三个 ssh.exe 打起来:环境变量里的暗战

这个坑挺隐蔽的,但是中招之后特别迷惑。症状是:你在 A 终端里跑 ssh 一切正常,在 B 终端里跑同一个命令就报一些莫名其妙的错,比如找不到 config、连不上特定主机、或者密钥加载失败。

根因是机器上装了多份 SSH 实现。最常见的三个来源是:系统自带的C:\Windows\System32\OpenSSH\ssh.exe、Git for Windows 附带的C:\Program Files\Git\usr\bin\ssh.exe、以及 WSL 或者 msys2 里的。它们的配置文件路径、默认算法策略、对某些参数的支持程度都不一样。

诊断方法:

where.exe ssh Get-Command ssh -All | Select-Object Source

把所有能搜到的列出来,看顺序。PATH 里排前面的优先。如果发现用的是 Git 那个而不是系统的,又想让某些场景用系统的,两条路:要么调整 PATH 顺序,要么在所有地方都用绝对路径调用。

这个问题在配 VS Code Remote-SSH 的时候特别容易撞上。VS Code 默认用 PATH 里的 ssh,但它也提供了一个remote.SSH.path设置项,可以显式指定用哪个 ssh.exe。遇到 Remote-SSH 连不上但命令行能连的情况,第一件事就是去查这一项,十有八九是指向了另一个 ssh。

8. 上线之后:加固与长期维护的几个习惯

8.1 稳定运行一周后再做的四件加固

新增的服务不要一上来就把安全策略拉到最紧,那样出问题的时候你会连救火的路都没有。我的做法是:先保证能用,稳定跑一周,再逐项加固,每次改一项,改完观察一天。

四项加固按优先级排:

改端口。把 22 换成比如 2222 这种不常见的值。这个不是靠"隐藏"来防护,而是把扫描器那种无差别扫全网 22 端口的噪声过滤掉,日志能干净很多。改完记得同步改防火墙规则。

关密码认证。前提是公钥登录已经全面验证通过,而且你有别的通道(RDP 或者第二个 SSH 会话)能进去救火。改的方法是配置文件里把PasswordAuthentication设成 no。

限制可登录用户。在配置文件里加AllowUsers或者DenyUsers,把能登录的账户收敛到一个白名单里。默认情况下所有本地账户都能登录,这对一台有二十个账户的服务器来说范围太大了。

提高日志级别。把LogLevel从默认的INFO提到VERBOSE,这样每次登录尝试的详细信息都会记录,包括用了哪把密钥、从哪个 IP 来的。日志量会涨,但对审计和事后追查很有价值。

8.2 日志留存和审计的做法

Windows 事件日志默认有大小限制,OpenSSH/Operational这个通道默认好像只有 1MB 左右,连接多的话几天就滚掉了。要长期留存,得先把这个通道的容量调大:

wevtutil sl OpenSSH/Operational /ms:104857600

/ms后面的单位是字节,这个例子是 100MB。调大之后,一般能用几个月。再长的话就得配日志转发,把事件统一收集到一台日志服务器上,但这个就属于更大范围的话题了,单机场景下把容量调大基本够用。

日常查看最近登录记录:

Get-WinEvent -LogName 'OpenSSH/Operational' -MaxEvents 100 | Where-Object { $_.Message -match 'Accepted|Failed' } | Select-Object TimeCreated, Message

这条命令能筛出成功和失败的认证记录,日常巡检看一眼就够了。

8.3 升级和卸载别留尾巴

升级这件事,如果你走的是可选功能方式,那跟着 Windows 更新走就行,不用管。如果是 MSI 或者 zip 部署的,就得自己维护。检查当前版本:

ssh -V sshd -V

升级时先停服务,替换文件,再启动。注意配置文件在C:\ProgramData\ssh,升级过程不会动它,但主机密钥也在那个目录,千万别在清理旧文件的时候顺手删了。删了的话所有客户端都会收到主机密钥变更警告,很麻烦。

卸载的话,按安装方式对应处理:

Remove-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
msiexec /x OpenSSH-Win64-vX.X.X.X.msi /quiet

卸完之后,C:\ProgramData\ssh这个目录不会自动删除,里面还留着主机密钥、过去积累的 authorized_keys。如果不打算再装了,手工清理掉;如果只是暂时卸载,留着也行,重装之后能直接复用,省得客户端重新确认指纹。另外记得把防火墙规则也删掉:

Remove-NetFirewallRule -Name "OpenSSH-Server-In-TCP"

我个人在几台机器上跑 Windows OpenSSH 服务端已经两年多了,最深的体会是:这东西本身的稳定性非常好,出问题的地方九成都不在 OpenSSH 身上,而是卡在权限模型和路径细节上。管理员组那套独立的密钥路径机制、ACL 的继承规则、多份 ssh 互相干扰,这三样基本覆盖了我遇到过的所有故障。把这些搞清楚之后,剩下的就是常规的配置调优,反而没什么好操心的了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询