1. 从一台没有图形界面的Windows Server说起:装OpenSSH到底要解决什么
几年前接手过一批运维任务,其中一台是跑在机房里的Windows Server,没有图形界面,日常只能靠远程桌面。问题在于,业务脚本部署在另一台Linux跳板机上,需要定期把生成的报表目录同步过去。RDP传文件慢、断线重连还容易中断,最靠谱的做法其实是让这台Windows也能被ssh/scp直接访问。于是就有了这篇内容要聊的事:在Windows上安装OpenSSH,把服务端和客户端都配起来。
OpenSSH在Windows上早已不是外挂级别的存在。从Windows 10 1809和Windows Server 2019开始,微软把OpenSSH客户端做成了系统内置组件,服务端则以"可选功能"的形式提供。这意味着大部分情况下你根本不需要去第三方站点下载什么安装包,动动命令就能装上。但真到了实操环节,坑还是不少:有的机器装完服务起不来,有的连上了却被立刻踢掉,有的密钥怎么放都不生效。
这篇内容适合三类人:需要在Windows上开sshd做远程运维的工程师、要写自动化脚本调用ssh/scp的开发、以及接手别人配好的Windows机器但看不懂配置的维护者。下面我会把在线安装、离线部署、配置调整、免密登录和排障这几块拆开讲,每一条都尽量说清楚"为什么这么做",而不是只丢一串命令。
提示:Windows上的OpenSSH分为客户端和服务端两个独立组件,装了客户端不代表能当服务器用,这是最常见的认知偏差,后面会单独展开。
2. 动手前先摸清底数:客户端和服务端各在不在
2.1 一条命令看清两个组件的安装状态
很多人一上来就敲ssh命令,发现能用,就以为服务端也装好了。实际上内置的那份只是客户端,用来从Windows连别人;要让别人连进这台Windows,得额外装OpenSSH Server。判断方法很简单,用管理员权限打开PowerShell,执行:
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'输出里会出现两行,形如OpenSSH.Client~~~~0.0.1.0和OpenSSH.Server~~~~0.0.1.0,后面的State字段会显示Installed或NotPresent。这一步的价值在于:先确定你要装的是哪一个,避免装完发现方向错了。我见过有人折腾半天服务端,结果他的需求只是从Windows连Linux,装个客户端就够了。
顺便再确认一下版本:
ssh -V这条命令会打印类似OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.2的信息。记下这个版本号,后面排查兼容性问题时要用到。
2.2 Windows版本与OpenSSH能力的对应关系
不同Windows版本能拿到的OpenSSH版本差别不小,这直接影响你能用哪些特性。比如较老的Windows Server 2016,系统自带的可选功能里根本没有OpenSSH条目,只能走离线安装那条路。而Windows 10 1809之后的版本,内置的客户端版本会随系统更新一起走。
| 系统版本 | 内置客户端 | 可选功能服务端 | 建议做法 |
|---|---|---|---|
| Windows 10 1809+ | 有 | 有 | 直接在线安装 |
| Windows Server 2019+ | 有 | 有 | 直接在线安装 |
| Windows Server 2016 | 无 | 无 | 离线部署Win32-OpenSSH |
| Windows 7 / 2008 R2 | 无 | 无 | 离线部署,且要注意API兼容 |
注意:Windows Server 2016这类系统里,即便你手动补了OpenSSH的二进制文件,也可能因为缺少某些系统调用而出现服务启动失败。这种时候优先考虑升级系统或用文件同步类工具替代,不要硬扛。
这里补一个经验:如果你拿到的是一台加固过的机器,可选功能列表可能是被策略裁剪过的,Get-WindowsCapability返回空或者报错。遇到这种情况,直接跳到离线安装那一节,别在在线路径上耗时间。
3. 在线安装:从可选功能到服务真正跑起来
3.1 图形界面路径适合偶尔用一次的人
路径是"设置 → 应用 → 可选功能 → 添加功能",在搜索框里输入OpenSSH,会看到"OpenSSH 客户端"和"OpenSSH 服务器"两个条目,选中服务器那个点安装即可。整个过程一两分钟,适合只是临时搭个环境、不想记命令的人。缺点是图形界面在Server Core版本上是没有的,而且批量部署时效率太低。
3.2 PowerShell命令才是批量部署的正解
实际工作中我更推荐命令行,一是可复制、可写进脚本,二是能明确知道每一步的结果:
# 安装服务端 Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 # 安装客户端(如果确实没装) Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0执行完看到Online : True、RestartNeeded : False就算成功了。如果安装中途失败,或者状态卡在InstallPending,可以用修复命令重置一下:
Repair-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0需要卸载时把动词换成Remove-WindowsCapability即可,注意卸载服务端会一并移除C:\ProgramData\ssh下的配置,操作前先备份。
3.3 服务启动、开机自启与默认Shell三件事
装完只是文件到位,服务还得手动拉起来,并且设置成开机自启,否则重启一次就失效:
Start-Service sshd Set-Service -Name sshd -StartupType 'Automatic' Get-Service sshd第三行用来确认状态是Running。这一步做完,理论上已经可以从别的机器ssh连过来了,但连接进去后大概率会看到一个黑漆漆的cmd窗口,因为Windows版sshd的默认Shell是cmd.exe。想换成PowerShell,改注册表:
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell ` -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" ` -PropertyType String -Force改完不需要重启服务,下次新建连接就会生效。为什么要费这一步?因为cmd对管道、变量展开、脚本执行的支持都很弱,你ssh过去本来就是要跑脚本或做批量操作的,用cmd会处处受限。这个细节很多教程都不提,但实际用起来差别很大。
4. 离线安装:把Win32-OpenSSH手工搬进目标机器
4.1 取包这件事要走官方渠道
完全断网的机器没法用可选功能。这时需要从微软官方的PowerShell/Win32-OpenSSH项目页面下载对应版本的zip包,比如OpenSSH-Win64.zip。下载时注意区分x64和x86,虽然现在x86的机器很少,但老设备上确实存在。拿到包之后,建议在有网络的机器上先解压检查一下目录结构,确认里面有sshd.exe、ssh.exe、install-sshd.ps1这几个关键文件,再拷进内网。
拷进去的方式可以用U盘,也可以走堡垒机的中转目录。我个人的做法是先把包放到C:\Users\Public\Downloads这类公共路径,解压验证完再挪到最终位置,避免解压一半发现包损坏又要重新拷贝。
4.2 定位解压与注册服务
最终目录我一般放在C:\Program Files\OpenSSH,路径里不要有空格和中文,否则某些脚本调用会出现诡异问题。解压完成后,进入该目录,用管理员权限的PowerShell执行:
powershell.exe -ExecutionPolicy Bypass -File .\install-sshd.ps1这个脚本会做几件事:注册sshd服务、生成主机密钥、调整文件权限、设置服务为手动启动。执行完再重复上一节的服务启动和自启设置,然后把C:\Program Files\OpenSSH加进系统PATH,这样在任何目录下都能直接调用ssh/scp。
$env:Path += ";C:\Program Files\OpenSSH" [Environment]::SetEnvironmentVariable("Path", $env:Path, "Machine")4.3 权限、路径与两个容易翻车的点
离线安装最常见的两个问题,第一个是脚本执行被策略拦下。如果你看到"无法加载文件,因为在此系统上禁止运行脚本",说明ExecutionPolicy是Restricted,上面那条命令里的-ExecutionPolicy Bypass就是专门绕开这个限制的。第二个是主机密钥生成失败,通常是因为解压目录的权限不对,或者解压时用了普通用户身份,导致后续服务账户没权限读密钥文件。
提示:离线安装的版本不会随系统更新自动升级,后面出现安全公告时需要你手动替换文件,建议把这个目录和版本号记进资产台账。
还有一点值得说:手动部署的版本,配置文件的默认路径依然是C:\ProgramData\ssh,而不是安装目录。很多人改错了地方的sshd_config,改完发现不生效,就是踩了这个坑。
5. sshd_config里真正需要动的几行
5.1 默认值逐条过一遍,别整份照抄别人的
C:\ProgramData\ssh\sshd_config是服务端的主配置。网上有很多"优化模板",动辄几十行改动,我实测下来大部分是没必要的。真正值得关注的就这么几项:
| 配置项 | 默认值 | 建议 | 原因 |
|---|---|---|---|
| Port | 22 | 按需改 | 端口冲突时改,改了要同步防火墙 |
| PasswordAuthentication | yes | 视情况 | 只走密钥就关掉 |
| PubkeyAuthentication | yes | 保持 | 免密登录的基础 |
| PermitRootLogin | 无 | 不适用 | Windows没有root概念,可忽略 |
| Subsystem sftp | 已配置 | 保持 | scp/sftp依赖它 |
改完配置一定要用Restart-Service sshd重启服务,光reload在某些版本上不生效。改之前先备份一份原文件,出问题能快速回滚。
5.2 主机密钥生成与更换
主机密钥的默认位置在C:\ProgramData\ssh\,包括ssh_host_rsa_key、ssh_host_ed25519_key等。首次安装脚本会自动生成,不需要你操心。但如果你把机器做了克隆(虚拟机模板那种),两台机器的主机密钥会完全一样,客户端连接时就会报"远程主机标识已更改"的警告。这时候需要删掉旧密钥重新生成:
Remove-Item C:\ProgramData\ssh\ssh_host_* ssh-keygen -t rsa -f C:\ProgramData\ssh\ssh_host_rsa_key -N "" ssh-keygen -t ed25519 -f C:\ProgramData\ssh\ssh_host_ed25519_key -N "" Restart-Service sshd客户端那边的known_hosts也要清理对应条目,否则一直报警告。这个坑在批量部署虚拟机模板时几乎必踩,提前规避能省不少事。
5.3 防火墙放行与端口连通性验证
服务起来了但连不上,十有八九是防火墙。较新版本的安装流程会自动创建一条入站规则,名字一般叫OpenSSH-Server-In-TCP。用这条命令确认:
Get-NetFirewallRule -Name *ssh*如果查不到,手动补一条:
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' ` -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22验证连通性别只靠直觉,按顺序来:先在服务器本机Test-NetConnection -ComputerName localhost -Port 22,确认服务在监听;再从客户端Test-NetConnection -ComputerName <服务器IP> -Port 22,这一步能区分是网络不通还是服务没起。这两步分开做,能省掉大量瞎猜的时间。
6. 客户端那边的功夫:密钥、config与免密登录
6.1 密钥对生成与公钥落到哪里
免密登录的核心是公钥放置位置正确。先生成密钥对:
ssh-keygen -t ed25519 -C "win-server-01"一路回车即可,私钥默认在%USERPROFILE%\.ssh\id_ed25519。然后把公钥内容追加到服务端的授权文件里。这里有个Windows特有的坑:普通用户和管理员用户用的授权文件不是同一个。普通用户放在自己的%USERPROFILE%\.ssh\authorized_keys,而管理员组成员统一读C:\ProgramData\ssh\administrators_authorized_keys。这个规则写在sshd_config末尾的Match块里:
Match Group administrators AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys很多人配了半天免密不生效,就是因为把公钥放进了个人目录,但登录的账户属于管理员组,sshd根本不看那个文件。这个细节值得单独记一笔。
6.2 用config文件把连接参数固化下来
每次敲完整命令太累,在客户端%USERPROFILE%\.ssh\config里配置别名:
Host win01 HostName 192.168.1.50 User opsadmin Port 22 IdentityFile ~/.ssh/id_ed25519 ServerAliveInterval 60 ServerAliveCountMax 3配完之后ssh win01就能直接连。ServerAliveInterval这两个参数是专门对付"闲置一会儿就被断开"的问题,很多网络设备会清理长时间无流量的会话,加了这个会定时发心跳保活。
6.3 权限不对导致密钥被忽略的排查
Windows版的OpenSSH对administrators_authorized_keys的ACL要求很严格:只允许Administrators组和SYSTEM账户访问,多了任何其他账户或继承权限,sshd就会直接忽略这个文件,且日志里给的信息很模糊。修正命令:
icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"执行完可以再用icacls看一下结果,确认没有多余条目。这个问题的隐蔽之处在于:文件内容明明是对的,客户端却一直提示要输密码,而服务端日志只写一句认证失败,不告诉你原因是权限。我第一次遇到时查了快一个小时。
7. 排障实录:连不上、连上被踢、版本对不上
7.1 连接被拒的三层排查顺序
遇到Connection refused或Connection timed out,按这个顺序走,别跳步:
- 服务层:
Get-Service sshd看服务在不在跑。不在跑就Start-Service,起不来就去看事件日志。 - 监听层:
netstat -ano | findstr :22看端口有没有被监听。如果被别的进程占了,要么改Port,要么停掉那个进程。 - 网络层:从客户端
Test-NetConnection测端口。不通就是防火墙或安全组的问题。
三层里最容易忽略的是第二层。有一次排查半天,最后发现是另一款软件占了22端口,sshd启动时直接失败退出,但服务状态显示得很含糊。
7.2 日志到底该看哪里
Windows版sshd的日志默认走Windows事件日志,位置在"事件查看器 → 应用程序和服务日志 → OpenSSH → Operational"。连不上、认证失败的具体原因基本都能在这里找到。如果觉得事件查看器点来点去麻烦,也可以用PowerShell直接过滤:
Get-WinEvent -LogName "OpenSSH/Operational" -MaxEvents 50 | Where-Object { $_.LevelDisplayName -in 'Error','Warning' } | Format-List TimeCreated, Message调高日志详细程度可以在sshd_config里加LogLevel VERBOSE,调试完记得改回去,不然日志量会涨得很快。
7.3 升级之后配置不兼容怎么办
OpenSSH的版本迭代比较快,新版会对某些老配置项告警甚至拒绝启动。比如某些加密算法被标记为弱算法后,老配置里还写着就会报错。处理思路是:先看服务启动失败的报错里点名了哪个配置项,再去对应版本的发行说明里查这个项的状态。常见做法是注释掉过时项,交给默认值处理,而不是强行保留。
注意:升级前务必备份
C:\ProgramData\ssh整个目录,包括配置和主机密钥。有些升级流程会覆盖配置,回滚时没有备份会很麻烦。
另外,客户端和服务端版本不需要完全一致,但跨大版本太多时可能出现算法协商失败,表现为连接建立到一半就断开,日志里能看到"no matching key exchange method found"之类的提示。这时候要么升级客户端,要么在服务端显式允许对应算法,两种方案各有权衡,生产环境优先选前者。
8. 把这套东西当长期资产来维护的一点个人习惯
配置好之后我建议做三件小事。第一,把C:\ProgramData\ssh目录和C:\Program Files\OpenSSH的版本号记进台账,下次出现安全公告时能快速定位哪些机器需要处理。第二,主机密钥生成后导出一份离线保存,机房重装系统时不至于让所有客户端的known_hosts全部失效。第三,对只走密钥登录的机器,在sshd_config里把PasswordAuthentication关掉,减少被暴力尝试的口子,同时配合登录失败次数限制。
我在实际使用中发现,Windows上的OpenSSH真正麻烦的从来不是"装"这个动作本身,而是装完之后那一堆权限、路径、组匹配的细节。把上面这些点过一遍,后续的维护成本会低很多。至于升级,我的习惯是先在测试机上跑一轮,确认现有连接方式都能用,再推到生产,毕竟这玩意儿一旦挂掉,远程运维的通道就断了,代价不小。