拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Windows 11 OpenSSH 服务端配置:改端口与密钥登录实战

Windows 11 OpenSSH 服务端配置:改端口与密钥登录实战

1. 为什么要在 Windows 11 上开一个 SSH 服务端

手里那台常年开机的 Windows 11 主机,硬盘里堆着项目代码、素材、虚拟机镜像和一堆懒得整理的下载文件。以前想从笔记本上取点东西,要么开共享文件夹,要么拿移动硬盘拷,命令行操作更是想都别想。后来我把 Windows 11 自带的 OpenSSH 服务端打开,顺手把默认的 22 端口改掉,再切到密钥登录,现在笔记本、平板、甚至手机都能一条命令连进来,sftp 拖文件、VS Code 直接开远程项目,体验完全是另一个档次。

这篇内容就是把这套流程从头到尾捋一遍:Windows 11 配置 SSH 服务器、修改监听端口、配置密钥登录。它不是什么高深的东西,前后加起来二十分钟足够,但有几个地方第一次做必然要绕一圈——比如管理员账户的公钥为什么放到自己家目录里就是不生效、改完端口为什么本机连得上外面连不上、密钥文件权限到底要不要收紧。这些细节网上零散的答案不少,但凑不成一条完整的链路。

适合谁看?三类人比较对口:一是手里有闲置 Windows 机器想当开发跳板或者文件中转站的;二是在局域网里想给主机加一个远程命令行入口,又不想装一堆第三方远程桌面软件的;三是已经会用 Linux 那一套 sshd 配置,换到 Windows 上发现路径、权限、服务管理逻辑全变了、想找对应关系的。整个过程用到的工具全是系统自带,不需要额外买什么。

1.1 内置 OpenSSH 与第三方方案的分界

Windows 从 Windows 10 1809 开始就把 OpenSSH 客户端做成了默认安装,服务端放在"可选功能"里等着你去开。到 Windows 11 这代,这套东西已经相当成熟,微软官方维护的 Win32-OpenSSH 分支一直在更新,sshd_config 的语法和 Linux 版本基本一致。

那为什么还有人去装第三方方案?主要是几个历史包袱:

  • 早期版本对 ed25519 密钥支持不完整,只能用 RSA;
  • 早几年的版本在非英文用户名下路径处理有问题;
  • 部分人习惯了图形化配置工具,觉得纯文本配置不直观。

现在的实际情况是,只要你系统更新到比较新的状态,ssh -V看一下版本号在 8.x 以上,ed25519 是完全没问题的。所以我个人建议:优先用系统内置的 OpenSSH 服务端,理由是它跟着系统更新走,不需要自己维护进程守护和升级,服务注册、防火墙规则、日志通道都是现成的。只有在两个例外情况下才考虑手动部署 zip 版:一是家庭版或者某些精简镜像里"可选功能"列表里搜不到 OpenSSH 服务器;二是你需要比系统自带更新的版本,想单独控制安装目录。

手动部署这套东西其实也不复杂,思路是从官方发布页拿压缩包,解压到一个固定目录,然后用自带的install-sshd.ps1脚本注册服务。但这条路后续维护成本会转移到你身上,每次升级都得手动替换文件、重启服务,所以除非有明确理由,否则不必折腾。

1.2 三个真正用得上的场景

我在实际使用中总结下来,Windows 上跑 SSH 服务端最实用的场景有三个,你可以对照一下自己的需求。

第一个是跨设备取文件和执行命令。一台台式机放在书房当"母机",笔记本在客厅或者外出,通过 SSH 连回去,sftp拉文件、跑个脚本、看下某个服务的状态。相比远程桌面,SSH 的带宽占用低得离谱,而且不需要图形界面环境,网络稍微差一点也能用。

第二个是给 VS Code 做远程开发后端。这是现在最主流的用法之一。VS Code 装一个 Remote-SSH 插件,配上主机地址和端口,就能把远程机器当成开发环境,代码存在台式机上,编辑体验在笔记本上。本地的笔记本不用装任何运行时和依赖,换个设备只要重新连一次就行。

第三个是自动化脚本的执行入口。比如你在 Windows 上跑定时备份、批量转码、Git 拉取,这些任务通过 SSH 触发一次就行,不用为了跑一条命令去开远程桌面。配合密钥登录免密,脚本里写一行ssh winbox "backup.bat"就完事了。

反过来说,有几种情况我不建议用 SSH:需要完整图形界面操作的(老老实实远程桌面)、需要在同一台机器上做高强度图形渲染的、以及只想传一两个文件并且是同一局域网且经常做的(共享文件夹更省事)。SSH 的强项是命令行和轻量文件传输,别指望它替代一切。


2. 装好服务端:可选功能、服务自启与第一次握手

这一节把服务端从"没有"变成"能连上"的状态。整个过程分三步:装功能、起服务、验证连通。听起来简单,但每一步都有个容易忽略的点。

2.1 两条安装路径的差别

图形界面的路径是:打开设置 → 系统 → 可选功能,点"查看功能",在搜索框里输入OpenSSH。你会看到两个条目——"OpenSSH 客户端"和"OpenSSH 服务器"。客户端通常已经装好了,你要加的是服务器。勾选、下一步、等它装完。

命令行路径更快,管理员权限打开 PowerShell:

# 先看看现在是什么状态 Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*' # 安装服务端 Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

两条路径的结果是一样的,但命令行有个优势:当图形界面搜索不到条目时,命令往往还能装。这在家庭版或者某些定制镜像上很常见——界面里的功能列表被裁剪过,但底层组件包其实还在系统里。如果命令行也提示找不到包,那才需要考虑前面说的手动部署。

装完之后验证一下版本和文件位置:

ssh -V Get-ChildItem C:\Windows\System32\OpenSSH\

正常情况下会看到sshd.exe、ssh-keygen.exe、sftp-server.exe、scp.exe这一套。要注意sshd.exe(服务端)和ssh.exe(客户端)是两个独立程序,别搞混。

注意:安装完成后不要急着改配置。先用默认状态连一次,确认基础链路是通的,再去动端口和认证方式。这样一旦后面出问题,你能确定是配置改错了,而不是安装本身就没成功。

2.2 sshd_config 到底在哪,默认值有哪些

这是从 Linux 转过来的人最容易懵的地方:Windows 上服务端的主配置文件不在/etc/ssh/,而在:

C:\ProgramData\ssh\sshd_config

C:\ProgramData是个隐藏目录,资源管理器里默认看不到,直接地址栏粘贴路径最快。同目录下还有ssh_host_rsa_key、ssh_host_ed25519_key这些主机密钥文件,以及一个administrators_authorized_keys(可能还不存在,后面会用到)。

打开 sshd_config,值得关注的就那么几行。默认状态下:

  • #Port 22—— 注释状态,意味着实际监听 22。注意这里有个容易犯的错误:如果你直接加一行Port 2222而不去动这行注释,那实际只监听 2222,因为#开头的就是注释,不参与解析。但为了配置清晰,我习惯把那行取消注释后直接改值。
  • #PubkeyAuthentication yes—— 注释状态,默认就是开启的。
  • #PasswordAuthentication yes—— 注释状态,默认开启密码认证。
  • Subsystem sftp sftp-server.exe—— 这行是生效的,别去动它,动了 sftp 就废了。
  • 文件末尾这几行:
Match Group administrators AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys

这几行是整篇内容里最关键的一个点。它的意思是:当登录用户属于本机 administrators 组时,服务端会忽略C:\Users\<用户名>\.ssh\authorized_keys,转而去读C:\ProgramData\ssh\administrators_authorized_keys这个公共文件。很多人把公钥按 Linux 的习惯往自己家目录一放,结果怎么都登不上,就是被这段 Match 规则截胡了。

顺便说个更高级的排查手段,不管配置多复杂,都可以用下面这条命令打印出最终生效的配置,比一行行看原文靠谱得多:

& "C:\Windows\System32\OpenSSH\sshd.exe" -T -f C:\ProgramData\ssh\sshd_config

它会把默认值和你的修改合并后输出,你能立刻看到port、passwordauthentication、authorizedkeysfile这些项的真实取值是多少。这个命令我在排查"改了没生效"类问题时用得最多。

2.3 本机回环测通不等于外部能连

配置没改之前,先把服务跑起来:

Start-Service sshd Set-Service -Name sshd -StartupType Automatic Get-Service sshd

第二条命令把启动类型设成自动,不然重启后服务不会自己起来,你会以为是配置坏了,其实是没启动。

然后在本机开一个终端测一下:

ssh 你的用户名@localhost

第一次连接会提示主机密钥指纹,输入 yes 接受。能弹出密码提示并登进去,说明服务端本身是活的。

这里有个特别容易误判的地方:localhost 的回环连接不走 Windows 防火墙的入站规则。也就是说,本机能连,完全不代表局域网里的另一台机器能连。防火墙那边有没有放行、放行的是哪个端口,是另外一回事。我见过不少人卡在这一步,折腾半天密钥和配置,最后发现是防火墙根本没开。

所以本机测通之后,一定要从另一台设备再做一次连接测试。这一步不能省。


3. 换掉 22 端口:配置、防火墙、验证的三件事

端口这件事,技术上就是把Port一改完事,但实际做的时候有三件事要同时对齐:配置文件、防火墙规则、以及验证方式。少做一件就会出现"看起来改了但连不上"的诡异现象。

3.1 Port 指令的写法与多端口并存

用管理员权限的文本编辑器打开C:\ProgramData\ssh\sshd_config。这里强调一下编辑器:用记事本改配置文件没问题,但保存时务必确认编码是 UTF-8。用某些老版本 PowerShell 的Set-Content -Encoding utf8会给文件加上 BOM 头,sshd 解析时可能报奇怪的语法错误。稳妥做法是直接用记事本或者支持无 BOM UTF-8 的编辑器手动保存。

把这一行:

#Port 22

改成(或者新增一行):

Port 2222

关于端口号的选择,我的建议是:

  • 避开 1024 以下的低端口,虽然 Windows 上不像 Linux 那样严格限制端口绑定权限,但这些端口冲突概率高;
  • 避开 135、139、445、3389、5357 这类系统服务常用端口;
  • 推荐落在 20000 到 60000 之间,随便挑一个自己好记的,比如生日数字、常用数字组合。

如果你想过渡期同时监听两个端口,那就写两行Port:

Port 22 Port 2222

sshd 会同时监听两个。这在灰度切换时很有用——老客户端继续走 22,新客户端换到 2222,等你把所有客户端都改完了再删掉 22 那行。不过要注意,这期间 22 端口的暴露面还在,切换完成后记得收尾。

配置改完之后,如果只是想让新端口生效,单纯重启服务就够了。补充一个小技巧:改配置前可以先做语法检查,避免重启失败导致服务起不来:

& "C:\Windows\System32\OpenSSH\sshd.exe" -t

返回空或者configuration OK就说明语法没问题。这条命令需要在管理员权限下跑,因为 sshd 要读取主机密钥文件。

3.2 防火墙规则要重新建一条,旧的要收掉

这是整个流程里最容易被忽略的一步。安装 OpenSSH 服务端时,系统会自动创建一条名叫OpenSSH-Server-In-TCP的入站规则,放行 TCP 22 端口。你把端口改成 2222 之后,这条规则不会跟着变,所以外部依然连不上。

先看看现有的规则:

Get-NetFirewallRule -DisplayName "*OpenSSH*" | Format-Table Name, DisplayName, Enabled, Direction, Action

新增一条针对新端口的规则:

New-NetFirewallRule ` -Name "OpenSSH-Server-TCP-2222" ` -DisplayName "OpenSSH Server (TCP 2222)" ` -Enabled True ` -Direction Inbound ` -Protocol TCP ` -Action Allow ` -LocalPort 2222 ` -Profile Private, Domain

这里的-Profile参数值得说一下。Windows 防火墙把网络分成 Domain(域)、Private(专用)、Public(公用)三类。SSH 服务端通常只需要在专用网络里可用,比如你家里的局域网。把规则限制在Private, Domain上,比默认的 Any 更克制一些——万一哪天笔记本带着规则连到咖啡店的公共网络,也不会直接对外开着端口。

旧规则的处理有两条路:如果你已经确定不再用 22,就把默认规则禁掉或者删掉:

# 方式一:禁用,保留下来方便随时恢复 Disable-NetFirewallRule -DisplayName "OpenSSH SSH Server (sshd)" # 方式二:直接删掉 Remove-NetFirewallRule -Name "OpenSSH-Server-In-TCP"

我个人的习惯是先禁用观察几天,确认没有遗漏的客户端还在用 22,再彻底删掉。直接删除虽然"干净",但真出问题的时候恢复要多花几分钟重建规则。

提示:如果规则加得完全正确,外部却依然连不上,可以临时把第三方安全软件退出一次做对照测试。某些安全套件会接管 Windows 防火墙或者自己拦截非常规端口的入站连接,这种情况下 Windows 防火墙里加多少规则都没用。

3.3 改完怎么确认它真的听在新端口上

改完配置、重启服务、加完防火墙规则,接下来要确认端口确实换了。

重启服务:

Restart-Service sshd

然后查监听状态,两种方式随便挑:

# 方式一:PowerShell 原生 cmdlet Get-NetTCPConnection -State Listen | Where-Object LocalPort -eq 2222 # 方式二:老朋友 netstat netstat -ano | findstr :2222

正常情况下能看到监听地址是0.0.0.0:2222(或者::的 IPv6 版本)。如果0.0.0.0后面跟的进程 ID 对应的正是 sshd,那服务端这边就对了。

还有一个隐藏的坑:端口被别的程序占了。如果 2222 被某个软件先行占用,sshd 启动时会绑定失败,服务状态看起来是"正在运行"但实际没监听成功。所以查监听结果这一步不能只查一遍就完事,得确认 PID 对应的是 sshd 本身:

Get-Process -Id (Get-NetTCPConnection -LocalPort 2222 -State Listen).OwningProcess

最后从另一台设备做一次完整验证,注意 SSH 客户端指定端口的参数是大写 P:

ssh -p 2222 你的用户名@主机IP

sftp也一样,都是大写-P:

sftp -P 2222 你的用户名@主机IP

如果客户端那边嫌每次敲-p 2222麻烦,下面第 6 节会讲怎么用客户端 config 把它省掉。


4. 密钥登录落地:生成、投放、收权、关密码四步

密码登录能用了,接下来做密钥登录。密钥登录的好处很直接:不用记密码、不怕暴力猜解、脚本里可以无人值守执行。整个流程分四步,其中第三步"收权"和第四步"关密码"是最容易出问题的。

4.1 选 ed25519 还是 RSA

密钥生成在客户端做(也就是你用来登录的那台机器),不是在服务器上做:

ssh-keygen -t ed25519 -C "win11-desktop-2024"

参数说明:-t ed25519指定算法,-C后面跟注释,随便写个能帮你识别这把钥匙用途的字符串,它会附加在公钥文件末尾。

为什么推荐 ed25519 而不是 RSA?几个实际理由:

对比项ed25519RSA 4096
公钥长度68 字符左右700+ 字符
签名速度快慢一个量级
抗侧信道算法层面有考虑依赖实现
兼容性需要较新的客户端/服务端几乎全兼容
密钥文件大小几百字节3KB 左右

公钥长度这个差异看起来无关紧要,但实际用起来是有感觉的:短公钥在复制粘贴、往authorized_keys里追加、在配置管理系统里存的时候都清爽很多。只有在对接一些老旧的嵌入式设备或者非常老版本的服务端时才需要退回 RSA,那时候用-t rsa -b 4096就行。

生成过程中会问保存路径和 passphrase。路径直接回车用默认的(C:\Users\<你>\.ssh\id_ed25519)就好。passphrase 建议设一个——它是加密私钥文件的密码,即使私钥文件泄露了,没有 passphrase 也用不了。当然如果你的目标就是无人值守的脚本登录,那就留空,但要清楚这意味着私钥文件本身就是凭证,得看紧。

生成完会得到两个文件:

  • id_ed25519—— 私钥,绝对不能外传,留在客户端。
  • id_ed25519.pub—— 公钥,就是要放到服务器上的那个。

4.2 普通用户与管理员用户的公钥投放路径完全不同

现在回到第 2.2 节说的那段 Match 规则。投放路径取决于你用来登录的那个 Windows 账户是不是管理员:

  • 普通用户:公钥放到C:\Users\<用户名>\.ssh\authorized_keys;
  • administrators 组成员:公钥放到C:\ProgramData\ssh\administrators_authorized_keys。

这个区分是 Windows 独有的,Linux 上不存在这个问题,所以从 Linux 转过来的人几乎百分百会踩一次。

普通用户的操作流程(在服务器上以该用户身份执行):

# 确保目录存在 New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.ssh" # 把公钥内容追加进去(公钥内容从客户端复制过来) $pub = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... win11-desktop-2024" Add-Content -Path "$env:USERPROFILE\.ssh\authorized_keys" -Value $pub -Encoding ascii

注意这里用的是-Encoding ascii。这里有个实际踩过的坑:用 Windows PowerShell 5.1 的Add-Content -Encoding utf8会写入 BOM 头,sshd 读这个文件时可能把 BOM 当成公钥内容的一部分,导致密钥解析失败,表现为"密钥明明放对了却认证不通过"。用 ascii 编码能规避这个问题,因为公钥内容本身就是纯 ASCII 字符。

管理员用户的操作流程:

$pub = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... win11-desktop-2024" Add-Content -Path "C:\ProgramData\ssh\administrators_authorized_keys" -Value $pub -Encoding ascii

如果文件不存在,Add-Content会自动创建。但创建出来的文件权限是继承自父目录的,这就引出下一步。

4.3 用 icacls 把文件权限收到最小

Linux 上 authorized_keys 的权限问题会直接报错,比如Permissions 0644 for 'xxx' are too open,一眼就知道该干嘛。Windows 上的表现要隐蔽得多:sshd 发现文件权限过于宽松时,往往不会给你明确的报错,而是直接当作"没有有效的公钥",客户端那边只看到一句Permission denied (publickey)。

所以不管是哪个路径的密钥文件,都建议主动收一次权限。对管理员用的公共文件:

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

对普通用户的密钥文件:

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

这两条命令做的事是:

  • /inheritance:r—— 断开从父目录继承来的权限;
  • /grant—— 只保留指定的两个主体有权限,其余全部清掉。

为什么管理员那个文件只给Administrators和SYSTEM?因为这个文件是所有管理员共用的,如果其中一个管理员能随意改写它,等于他能以另一个管理员的身份登录。SYSTEM必须留着是因为 sshd 服务本身是以 SYSTEM 身份运行的,它得能读这个文件。

验证一下收权结果:

icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys"

输出里应该只看到 Administrators 和 SYSTEM 两个条目,不能再出现Users、Authenticated Users、Everyone这类主体。

注意:有些教程会说"Windows 上不需要管权限",这个说法在当前版本上是不准确的。确实有一部分版本对权限检查比较宽松,但只要你遇到过"密钥内容完全正确却认证失败"的情况,第一件该做的事就是把权限收紧再试一次。

4.4 关掉密码认证的正确顺序

密钥能登上之后,才考虑关掉密码认证。顺序不能反,反了就有可能把自己锁在外面。

标准流程应该是这样:

  1. 先用密钥成功登录一次,确认无误;
  2. 保持这个 SSH 会话窗口不要关——这是你的安全绳;
  3. 另开一个窗口,修改C:\ProgramData\ssh\sshd_config,把PasswordAuthentication改成no;
  4. 重启服务:Restart-Service sshd;
  5. 用密钥再连一次,确认还能进;
  6. 确认没问题了,再关掉第 2 步那个备用窗口。

修改的内容就是这一行:

PasswordAuthentication no

如果原来是注释状态,把#去掉再改值。同时确保PubkeyAuthentication yes是开启的。

这里有个特别容易被忽略的坑:不要在自己的 SSH 会话里重启 sshd。Windows 上 sshd 会为每个连接派生一个子进程处理会话,重启服务的时候这些子进程会被一起带走,你当前的会话会直接断开。如果你正好是在这个会话里改的配置,而且新配置有问题,那就真的进不去了。所以配置改动尽量在本机的物理终端上做,或者至少保证有一个不通过 SSH 的入口。

真要是不小心把自己锁在外面了怎么办?如果机器物理可达,本机登录改回来就行。如果机器在另一个房间或者另一个城市,那就比较麻烦了——所以第 2 步那个"保持会话窗口不关"的习惯一定得养成。

关掉密码认证之后,还有个细节值得处理:Windows 账户本身的密码策略。既然 SSH 已经不用密码了,本机登录密码仍然要保留,但可以考虑把远程登录这条路彻底封掉。做法是在 sshd_config 里加:

AllowUsers alice bob

或者反过来:

DenyUsers guest test

AllowUsers是白名单,只允许列表里的用户登录,其余一律拒绝。这个比想象中有用——Windows 上可能有一堆账户你根本没意识到存在,比如安装某些软件时自动创建的。


5. 连不上时的排查链路:从客户端 -v 到服务端 DEBUG3

配置这种事,一次成功的概率其实不高,尤其是第一次做的时候。这一节把常见的失败表现和排查顺序整理出来,你可以按着这个链路一步步走,别东一榔头西一棒槌。

5.1 Permission denied (publickey) 的四个排查点

客户端报Permission denied (publickey)是最常见的一种。它只说明"密钥认证没通过",具体原因要靠下面四点逐个排除。

第一,客户端有没有把正确的密钥发出去。用-v参数看客户端日志:

ssh -v -p 2222 alice@192.168.1.50

在输出里找Offering public key这行,看看它列出的指纹是不是你要用的那把。如果客户端发的是别的密钥,或者压根没发(只看到Will attempt key: ...之后就没有Offering了),问题在客户端这边。常见原因是本地.ssh目录下堆了好几把密钥,客户端挨个试,试到被服务端拒绝次数上限。解决办法是在客户端 config 里指定IdentitiesOnly yes和IdentityFile。

第二,服务端读的文件对不对。用前面说的sshd -T打印生效配置,看authorizedkeysfile这一项指向哪里。如果你的用户名在 administrators 组里,而它指向的是C:\ProgramData\ssh\administrators_authorized_keys,那公钥就必须放那儿。

第三,文件内容和编码有没有问题。用文本编辑器打开那个文件看看:一行一条公钥,有没有被换行拆断、有没有 BOM 头乱码、有没有把私钥误当成公钥粘进去。私钥文件的开头是-----BEGIN OPENSSH PRIVATE KEY-----,公钥则是ssh-ed25519 AAAA...开头,别搞混。

第四,文件权限是否过宽。回到第 4.3 节,用 icacls 收一次权限再试。

这四点基本能覆盖九成以上的密钥认证失败。

5.2 打开 OpenSSH 操作日志与前台调试模式

客户端日志只告诉你"失败了",服务端日志才告诉你"为什么失败"。Windows 上 OpenSSH 有独立的事件日志通道,在事件查看器 → 应用程序和服务日志 → OpenSSH → Operational里。

默认情况下这个日志可能没有启用,用管理员权限打开 PowerShell 启用它:

wevtutil set-log "OpenSSH/Operational" /enabled:true

不过默认的日志级别信息量有限。要做深度排查,可以在 sshd_config 里临时加上:

SyslogFacility LOCAL0 LogLevel DEBUG3

改完重启服务,重新连一次,然后去事件查看器里看。DEBUG3 会打印出密钥协商、认证尝试、授权文件路径这些细节,基本上能一眼定位问题。

但说实话,事件查看器的体验不算好,翻日志比较费劲。我个人更常用的是前台调试模式——把服务停掉,直接在控制台里跑 sshd,日志会实时打在屏幕上:

Stop-Service sshd # 管理员权限下,前台运行,-ddd 是最高调试级别 & "C:\Windows\System32\OpenSSH\sshd.exe" -ddd -p 2222

这个窗口会一直挂着,你从另一个终端连过来,连接到什么程度、在哪一步被拒、读的是哪个文件、文件权限判定结果是什么,全部实时输出。排查完按 Ctrl+C 退出,再Start-Service sshd恢复正常。

这个方法的效率比翻事件日志高太多,建议把它当成排查这类问题的首选手段。

5.3 改了配置却没生效的常见原因

还有一种情况是"配置明明改了,行为一点没变"。常见的几个原因:

改的是用户级配置文件。Windows 上除了系统级的C:\ProgramData\ssh\sshd_config,用户家目录下还可以有~/.ssh/sshd_config。如果你不小心改错了文件,系统级的配置会覆盖它,你改的那份自然不生效。改之前先确认路径。

没重启服务。sshd 只会在启动时读取配置,改完必须重启。Restart-Service sshd一下,别指望它自动重载。

配置项被后面的 Match 块覆盖了。sshd_config 是自上而下解析的,Match块里的设置只对匹配到的连接生效,并且会覆盖全局设置。如果你的全局设置和 Match 块冲突,实际生效的是更具体的那个。

服务启动失败,实际跑的是旧进程或者压根没跑。改配置时手误打错一个字符,sshd 启动就失败,但因为系统里可能还有残留进程,看起来像是"服务在跑"。所以每次改完都用Get-Service sshd确认状态,配上sshd -T做语法验证。

防火墙那边没跟上。改端口的场景下,配置文件改了、服务重启了,本机测试也通,但外部还是连不上——九成是防火墙规则没同步更新。这个在第 3.2 节说过,单独拎出来再强调一次是因为它真的太常见了。


6. 日常使用打磨:默认 Shell、客户端别名与文件互传

到这一步服务端已经能用了,剩下的都是提升日常体验的事。这些不是必须做的,但做完之后使用频率会明显上升,因为顺手程度完全不一样。

6.1 把登录后的 Shell 换成 PowerShell

Windows 上 SSH 登录之后,默认进的是cmd.exe。对习惯了 PowerShell 的人来说这体验很割裂——管道语法不一样、命令不一样、连ls都要重新适应。改成 PowerShell 很简单,改注册表就行:

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

如果你装了 PowerShell 7(推荐),路径换成:

$shell = "C:\Program Files\PowerShell\7\pwsh.exe" New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value $shell -PropertyType String -Force

改完不需要重启 sshd,新登录的会话就会用新 Shell。已经连着的会话不受影响,退出重连一次即可。

这里有个反面例子值得提一下:有人想直接把默认 Shell 设成某个交互式脚本或者带参数的启动命令,这通常行不通。DefaultShell只接受一个可执行文件的完整路径,不接受参数。如果你需要登录后自动执行某些初始化操作,正确做法是在 PowerShell 的$PROFILE里写,而不是在注册表里堆参数。

还有一点,如果这个账户同时用于本地登录和远程登录,把远程 Shell 改成 PowerShell 不影响本地登录,本地还是走你的默认设置。两者是独立的。

6.2 客户端 config 写别名,衔接 VS Code Remote-SSH

每次都敲ssh -p 2222 alice@192.168.1.50太累,客户端 config 可以把这些都固化下来。在客户端的C:\Users\<你>\.ssh\config里加:

Host winbox HostName 192.168.1.50 Port 2222 User alice IdentityFile ~/.ssh/id_ed25519 IdentitiesOnly yes ServerAliveInterval 60 ServerAliveCountMax 3

逐项说明一下,因为好几个参数是有实际作用的:

  • Host winbox—— 起个别名,之后ssh winbox就等价于那一长串。
  • IdentitiesOnly yes—— 强制只使用IdentityFile指定的那把密钥。不加这个的话,客户端会把本地所有密钥挨个试一遍,服务端那边会记录大量失败尝试,有些加固配置会因此直接封 IP。
  • ServerAliveInterval 60—— 每 60 秒发一个心跳包。
  • ServerAliveCountMax 3—— 连续 3 次心跳没响应才断开。

后两个参数是解决"发呆一会儿连接就断了"的经典组合。因为长时间空闲的连接会被中间的网络设备清掉会话,加了心跳之后连接能一直挂着。注意这两项是客户端配置,写在服务端的 sshd_config 里也有对应的ClientAliveInterval,但管的是不同方向的事,别搞混。

配好之后,VS Code 的 Remote-SSH 也能直接吃这个配置。装了插件之后,远程资源管理器里会列出你 config 里的所有 Host,点一下就连上了,不用再手动填地址端口。

关于 VS Code Remote-SSH 有个实际问题要提醒:第一次连接时它会在服务端下载并安装vscode-server,这东西会占用几百 MB 到 1GB 左右的磁盘空间,装在用户目录下。如果你的系统盘比较紧张,建议先把用户目录迁到数据盘,或者定期清理~/.vscode-server里旧版本的目录。它不会自动清理历史版本,用久了会越堆越多。

另外,Remote-SSH 在 Windows 服务端上要求有 PowerShell 可用,这个条件默认就满足。但如果前面把默认 Shell 改成了某些非标准的东西,可能会影响插件的正常工作,改之前建议先确认一下自己是不是重度依赖 Remote-SSH。

6.3 sftp 与磁盘映射的取舍

文件传输这块,Windows 上主要有两条路。

第一条是 sftp。命令行操作,简单可靠:

sftp -P 2222 alice@192.168.1.50

进去之后就是熟悉的put、get、ls、cd:

sftp> lcd D:\Downloads sftp> cd /C:/Users/alice/Desktop sftp> put report.zip sftp> get data.csv

注意路径写法:Windows 上的盘符在 sftp 里表现为/C:/Users/...这种形式。这个反直觉的写法第一次见会愣一下。

图形化的 sftp 客户端也可以用,填主机、端口、用户名,密钥文件选上就行。适合不想记命令的场景。

第二条是 SMB 共享映射。把服务器的某个目录共享出来,客户端映射成网络驱动器,在资源管理器里像本地盘一样操作。适合大批量文件浏览和拖拽。

两者的取舍很清楚:sftp 传输单个文件、脚本自动化、跨平台的时候更合适;SMB 适合频繁的图形化文件浏览。但 SMB 在网络条件差的情况下体验会明显下降,而且共享权限和 NTFS 权限是两套东西,配置起来比 SSH 密钥麻烦。所以我的习惯是——日常小文件用 sftp,需要大幅浏览的时候才临时开共享。


7. 踩过的坑和长期维护上的一些取舍

最后聊聊那些文档里不太会写、但实际用起来一定会遇到的东西。

7.1 中文用户名、WSL 冲突、端口占用

中文用户名。这是我最想强调的一条。如果你的 Windows 账户名是中文,OpenSSH 在路径处理上出问题的概率会明显升高,表现五花八门——有的版本是认证直接失败,有的是 sftp 进不去某个目录,有的是authorized_keys读不到。这个问题排查起来很折腾,因为日志里给的信息不一定能指向编码问题。我的建议是:专门建一个英文用户名的账户,只用于 SSH 登录。这个账户不需要是管理员,反而更合适——普通用户的公钥路径更简单,不涉及administrators_authorized_keys那套逻辑。日常操作需要提权的时候,再在这个会话里用提权命令就行。

WSL 冲突。如果你在 Windows 上装过 WSL,并且在里面装过 openssh-server,那就存在两套 sshd:Windows 原生的一套,WSL 发行版里的一套。两者是独立的系统,配置文件、密钥、服务管理命令全都不一样。更麻烦的是端口冲突——WSL 里的 sshd 如果也监听 22 或者同一个端口,Windows 原生这边的绑定就会失败。排查的时候先确认你现在改的是哪一套,别在 WSL 里改了半天配置,然后去 Windows 服务里重启服务。

端口占用。前面提过一次,这里再补一个实操细节。Windows 上有一个概念叫"保留端口范围"(excluded port range),某些端口会被系统预留,普通进程绑定不了。用这条命令可以查:

netsh int ipv4 show excludedportrange protocol=tcp

如果挑的端口正好落在某个保留区间里,服务会启动失败但报错信息可能很含糊。换成区间外的端口就好了。

IP 地址变动。局域网里如果主机用的是 DHCP 分配的地址,路由器重启或者租约到期后地址可能变化,客户端 config 里写死的HostName就失效了。解决办法是在路由器上给这台机器做 DHCP 保留,或者在 Windows 里直接设一个局域网内的静态地址。这一步和 SSH 本身无关,但属于"配好之后能不能一直用"的关键前提。

7.2 长期运行下值得定期做的事

服务跑起来之后基本不需要管,但有几件事隔一段时间看一眼会更省心。

定期清理known_hosts里的过期条目。如果服务端重装过系统或者重新生成了主机密钥,客户端会报主机密钥不匹配的警告,然后拒绝连接。这是正常的保护机制,不是 bug。处理方式是找到报错里提示的那一行号,在客户端的known_hosts文件里删掉对应条目,然后重新连接接受新指纹:

ssh-keygen -R "[192.168.1.50]:2222"

注意主机加端口的时候要用[host]:port这种带方括号的写法,不然-R找不到对应的条目。

定期看一眼认证日志。前面启用了 OpenSSH/Operational 日志的话,偶尔翻一眼有没有大量失败尝试。虽然密钥登录本身已经很难被猜中,但日志能告诉你端口是不是被扫到了,这有助于判断当前的暴露面。

更新系统后重连一次。Windows 的大版本更新偶尔会把可选功能重置,或者更新 OpenSSH 版本导致配置格式有细微变化。养成习惯:大更新之后连一次,确认服务还在、配置还生效。用sshd -T快速过一遍。

备份配置和密钥。C:\ProgramData\ssh\整个目录值得定期备份,里面装着主机密钥和配置文件。主机密钥丢了意味着所有客户端都要重新接受指纹,虽然不影响使用,但会烦人。放进一个加密的备份位置就行,注意这个目录里的私钥文件(ssh_host_*_key)本身不能泄露。

关于安全加固的取舍。网上有很多"SSH 加固清单",动辄几十条。我的实际建议是分层次:必做的是密钥登录 + 关密码认证 + 权限收权,这三件事覆盖了绝大部分风险;可选的是AllowUsers白名单、改端口、限制防火墙 Profile,这些是纵深防御,做了更好但优先级低于前三件;至于失败次数锁定、双因素认证这些,在局域网自用的场景下投入产出比不高,除非这台机器是长期对外暴露的。

最后分享一个我自己的小习惯:给每一台机器的 SSH 密钥都用不同的注释和文件名,比如id_ed25519_winbox、id_ed25519_homepc。这样一旦某一把密钥需要作废,你能立刻知道该删哪台机器上的哪个文件,而不用去猜"这把id_ed25519到底是配给谁的"。配合客户端 config 里的IdentityFile指定,管理起来非常清爽。这个习惯在机器数量超过三台之后价值会陡然上升——我在只有两台机器的时候完全不当回事,等到了第五六台的时候,回头整理密钥花的时间比当初多付出十秒钟要多得多。

返回列表