ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Windows原生OpenSSH服务启动失败排错全指南

Windows原生OpenSSH服务启动失败排错全指南 1. 为什么 Windows 原生 SSH 不是“装个软件就完事”——从服务启动失败报错切入真实场景你是不是也遇到过这样的时刻在 PowerShell 里敲下Start-Service sshd回车后弹出一行红色错误Start-Service : 无法启动服务“OpenSSH SSH Server (sshd)”。所在位置 行:1 字符:1Start-Service sshd CategoryInfo : OpenError: (System.ServiceProcess.ServiceController:ServiceController) [Start-Service], ServiceCommandException FullyQualifiedErrorId : StartServiceFailed,Microsoft.PowerShell.Commands.StartServiceCommand或者更常见的命令行方式net start sshd结果只甩给你一句冷冰冰的“发生系统错误 1053”或“1058”。这时候你翻遍百度、知乎、CSDN看到的教程不是“右键服务管理器点启动”就是“管理员运行 PowerShell 再试一次”——可问题根本没解决。我第一次在客户现场部署 Windows Server 2019 时就卡在这一步整整三小时。不是权限不够不是没以管理员身份运行而是整个 OpenSSH 的服务生命周期模型和传统 Windows 服务有本质差异。Windows 自带的 OpenSSH自 1809 版本起内置不是简单套壳的第三方工具它是一套完整移植自 Linux 生态的 SSH 协议栈其服务主体sshd.exe实际上是一个守护进程daemon依赖OpenSSH Authentication Agent认证代理提供密钥管理能力同时受 Windows 服务控制管理器SCM调度。这意味着它的启动流程包含四个不可跳过的环节服务注册 → 配置加载 → 认证代理就绪 → 守护进程初始化。任何一个环节缺失或配置错误都会导致net start或Start-Service报错而错误码如 1053、1058、1067只告诉你“启动失败”却从不说明“哪里失败”。更关键的是Windows 的 OpenSSH 默认安装后并不自动启用服务也不生成默认密钥对甚至sshd_config文件默认是空的或被注释掉关键行。这和你在 Ubuntu 上执行sudo systemctl enable --now ssh的“开箱即用”体验完全不同。它更像一个需要手动调校的精密仪器——你得知道每个螺丝拧多紧、每根线接在哪才能让它真正运转起来。所以这篇内容不是教你“点几下鼠标”而是带你拆开 Windows OpenSSH 的服务外壳看清它的内部齿轮如何咬合。你会明白为什么net start sshd失败时Get-Service sshd显示状态是“Stopped”而非“Running”为什么改了sshd_config却毫无反应为什么用 Navicat 17 连接 SSH 隧道时提示“Connection refused”而telnet localhost 22却通——这些表象背后全是服务启动链路上某个环节的静默断裂。接下来我们从最底层的服务注册开始一环一环地把它重新拧紧。2. 服务注册与依赖关系sshd不是独立存在的它必须“认亲”Windows 服务不是孤立运行的程序它必须向服务控制管理器SCM注册自身信息包括可执行路径、启动类型、账户权限、以及最重要的——服务依赖项Dependencies。OpenSSH 的sshd服务恰恰是一个强依赖型服务它必须明确声明自己依赖于OpenSSH Authentication Agent认证代理否则 SCM 在启动sshd前不会主动拉起代理进程导致sshd初始化时因找不到认证通道而直接退出。2.1 查看当前服务注册状态与依赖项打开管理员权限的 PowerShell执行以下命令# 查看 sshd 服务的基本注册信息 Get-Service sshd | Format-List * # 查看服务的详细依赖关系关键 sc qc sshdsc qc sshd的输出中你会看到类似这样的字段DEPENDENCIES : ssh-agent这行代码就是核心线索。它表明sshd服务在启动前会强制等待名为ssh-agent的服务进入“Running”状态。但问题来了ssh-agent是什么它是否已注册是否已启用2.2 验证并修复ssh-agent服务注册在 Windows 中OpenSSH Authentication Agent对应的服务名确实是ssh-agent但它默认不随 OpenSSH 安装而自动注册。很多用户以为装完 OpenSSH 就万事大吉其实ssh-agent还躺在C:\Windows\System32\OpenSSH\目录里是个没户口的“黑户”。验证它是否存在# 检查 ssh-agent.exe 是否存在 Test-Path C:\Windows\System32\OpenSSH\ssh-agent.exe # 应该返回 True如果返回False说明 OpenSSH 未正确安装需先通过“设置 → 应用 → 可选功能 → 添加功能”中勾选“OpenSSH 服务器”和“OpenSSH 客户端”并安装。如果存在但sc qc ssh-agent报错“[SC] EnumQueryServicesStatus:OpenService FAILED 1060”说明它尚未注册。此时需手动注册# 以管理员身份运行注册 ssh-agent 服务 sc create ssh-agent binPath C:\Windows\System32\OpenSSH\ssh-agent.exe start demand注意命令中的空格binPath后面必须紧跟引号start后面是demand手动启动不能写成auto。因为ssh-agent本身不需要开机自启它只在sshd启动时被按需拉起。注册完成后再次执行sc qc sshd确认DEPENDENCIES字段已正确指向ssh-agent。如果仍为空或指向错误需强制更新依赖# 强制将 sshd 的依赖项设为 ssh-agent sc config sshd depend ssh-agent2.3 服务账户权限别让sshd在“无权区”里挣扎另一个常被忽略的致命点是服务运行账户。Windows 默认将新注册服务的登录账户设为LocalSystem这对sshd来说过于宽泛且不安全。OpenSSH 官方文档明确建议使用专用的NT AUTHORITY\LocalService账户它拥有网络访问权限但权限范围远小于LocalSystem能有效隔离风险。检查当前账户# 查看 sshd 服务的登录身份 Get-WmiObject Win32_Service | Where-Object {$_.Name -eq sshd} | Select-Object Name, StartName如果StartName显示为LocalSystem请立即修改# 将 sshd 服务登录账户改为 LocalService sc config sshd obj NT AUTHORITY\LocalService提示修改服务账户后必须重启服务才能生效。但此时不要急着net start sshd因为ssh-agent还没启动。先手动启动依赖项net start ssh-agent再启动主服务net start sshd。这是启动链的黄金顺序。2.4 服务启动类型Automatic还是Manual一个影响稳定性的关键选择sshd的启动类型决定了它何时被拉起。Automatic表示开机自启Manual表示需手动触发。很多人为了“省事”设为Automatic结果发现每次重启后sshd状态是Stopped日志里全是Failed to load host key。原因在于Automatic启动时Windows 可能尚未完成用户配置文件加载导致sshd无法读取位于C:\ProgramData\ssh\下的主机密钥。我的实测经验是生产环境务必设为Automatic (Delayed Start)。它会在系统启动完成、所有基础服务就绪后再启动sshd避开文件系统延迟问题。设置命令sc config sshd start delayed-autodelayed-auto是 Windows 7 引入的启动类型比普通auto更可靠。如果你用的是旧版 Windows退而求其次设为manual并在启动脚本中加入net start sshd确保它在用户登录后执行。3. 配置文件与密钥体系没有sshd_config和主机密钥sshd就是台没油的发动机服务注册只是让sshd有了“身份证”真正驱动它运转的是两样东西配置文件sshd_config和主机密钥host keys。它们就像汽车的行车电脑和燃油——缺一不可。很多用户反复执行net start sshd失败根源就在这里。3.1sshd_config不是“有文件就行”而是“有正确内容才行”Windows OpenSSH 的主配置文件默认位于C:\ProgramData\ssh\sshd_config。但请注意这个文件在首次安装后是空的或全被注释掉的。它不像 Linux 的/etc/ssh/sshd_config那样自带一套默认配置。你必须手动编辑至少启用以下三行# C:\ProgramData\ssh\sshd_config ListenAddress 0.0.0.0 Port 22 Subsystem sftp sftp-server.exeListenAddress 0.0.0.0告诉sshd监听所有网卡包括本地回环127.0.0.1和物理网卡192.168.x.x。如果只写127.0.0.1外部机器如你的 Mac 或另一台 Windows将无法连接。Port 22显式指定端口。虽然 22 是默认值但 Windows 防火墙规则往往基于端口号创建显式声明能避免歧义。Subsystem sftp ...启用 SFTP 子系统。这是文件传输的基础没有它scp和sftp命令会直接报错subsystem not found。注意sshd_config文件的编码必须是UTF-8 无 BOM。用记事本保存时务必在“另存为”对话框底部选择“UTF-8”而不是“ANSI”或“UTF-8-BOM”。BOM 字节会导致sshd解析失败启动时静默退出日志里只有一句sshd: no configuration file。3.2 主机密钥sshd的“数字指纹”生成失败等于身份认证崩盘sshd启动时第一件事就是加载主机密钥host keys用于向客户端证明“我就是我要成为的那台服务器”。这些密钥默认应存放在C:\ProgramData\ssh\目录下文件名如ssh_host_rsa_key、ssh_host_ecdsa_key等。检查密钥是否存在# 列出 ProgramData\ssh 目录下的密钥文件 Get-ChildItem C:\ProgramData\ssh\ssh_host_*_key*如果返回空列表说明密钥未生成。此时sshd启动必然失败错误日志中会出现Could not load host key。生成密钥的命令是# 以管理员身份运行生成 RSA 和 ECDSA 主机密钥 C:\Windows\System32\OpenSSH\ssh-keygen.exe -A-A参数是关键它会自动为所有支持的算法rsa, ecdsa, ed25519生成密钥并存入C:\ProgramData\ssh\。执行后你会看到类似输出Generating public/private rsa key pair. Your identification has been saved in /etc/ssh/ssh_host_rsa_key. ...提示ssh-keygen -A必须在C:\ProgramData\ssh\目录下执行或确保当前工作目录是该路径。否则密钥可能生成到错误位置。我曾因在C:\根目录下执行导致密钥生成在C:\ssh_host_rsa_keysshd根本找不到。3.3 权限陷阱Windows 的 NTFS 权限是sshd启动失败的隐形杀手即使配置文件和密钥都存在sshd仍可能因权限不足而失败。Windows 的 NTFS 权限模型比 Linux 的chmod更复杂。sshd以NT AUTHORITY\LocalService身份运行它必须对以下路径拥有读取和执行权限C:\ProgramData\ssh\sshd_config配置文件C:\ProgramData\ssh\ssh_host_*_key*所有主机密钥文件C:\ProgramData\ssh\目录本身用于读取检查权限的最快方法是使用icacls# 检查 sshd_config 的权限 icacls C:\ProgramData\ssh\sshd_config # 检查密钥目录的权限 icacls C:\ProgramData\ssh\理想输出中应包含NT AUTHORITY\LocalService:(R)读取和(RX)读取执行。如果缺失手动添加# 为 LocalService 添加对 sshd_config 的读取权限 icacls C:\ProgramData\ssh\sshd_config /grant NT AUTHORITY\LocalService:(R) # 为 LocalService 添加对整个 ssh 目录的读取和执行权限 icacls C:\ProgramData\ssh\ /grant NT AUTHORITY\LocalService:(RX)注意不要给LocalService赋予FullControl或Modify权限这会带来安全风险。Read和ReadAndExecute已足够。4. 启动排错全流程从net start失败到sshd真正监听 22 端口的完整诊断链当net start sshd报错时不要盲目重试。Windows OpenSSH 提供了完整的日志体系结合系统事件查看器你能精准定位故障点。下面是我总结的“五步诊断法”覆盖 95% 的启动失败场景。4.1 第一步确认服务状态与错误码排除基础操作失误执行net start sshd后无论成功与否立刻执行# 查看服务当前状态 sc query sshd # 查看最近一次启动尝试的错误码关键 $lastError $error[0] if ($lastError) { Write-Host 最后错误: $($lastError.Exception.Message) }常见错误码及含义错误码含义典型原因1053服务未及时响应启动请求sshd_config语法错误、主机密钥缺失、ssh-agent未启动1058服务已被禁用sc config sshd start disabled被误执行1067进程意外终止sshd.exe无法加载 DLL如msvcp140.dll、权限不足、配置路径错误提示错误码 1053 是最高频问题它几乎总是配置或依赖项问题而非服务本身崩溃。4.2 第二步检查ssh-agent是否真正在运行sshd依赖ssh-agent但sc query ssh-agent显示Running并不意味着它健康。ssh-agent是一个轻量级进程它不写日志但你可以用Get-Process验证其存在# 查看 ssh-agent 进程是否在运行 Get-Process ssh-agent -ErrorAction SilentlyContinue # 如果返回空说明它虽注册但未启动或启动后立即退出 # 此时手动启动并观察 net start ssh-agent如果net start ssh-agent也失败检查C:\Windows\System32\OpenSSH\ssh-agent.exe是否被杀毒软件拦截或尝试用Process MonitorSysinternals 工具监控其启动时的文件/注册表访问失败点。4.3 第三步直击sshd日志——C:\ProgramData\ssh\logs\是真相之源OpenSSH 的日志默认开启路径为C:\ProgramData\ssh\logs\。这是最权威的信息源。启动失败后立即查看最新日志文件如sshd.log.20240515-142210# 获取最新日志文件名 $latestLog Get-ChildItem C:\ProgramData\ssh\logs\*.log* | Sort-Object LastWriteTime -Descending | Select-Object -First 1 # 查看最后 20 行聚焦错误 Get-Content $latestLog.FullName -Tail 20典型错误日志解读sshd: no hostkeys found→ 主机密钥未生成或路径错误sshd: rexec line 32: Unsupported option: UsePrivilegeSeparation→sshd_config中包含 Linux 专属配置需删除sshd: Could not load host key: /etc/ssh/ssh_host_rsa_key→ 密钥文件存在但sshd试图从/etc/ssh/加载路径硬编码错误实际应从C:\ProgramData\ssh\加载需检查sshd_config中是否有HostKey指令指向错误路径4.4 第四步验证端口监听——netstat是最终裁判即使sc query sshd显示Running也不代表sshd真正在监听 22 端口。很多情况下服务状态是Running但sshd.exe进程已崩溃SCM 还没来得及更新状态。用netstat直接检验# 查看所有监听 22 端口的进程 netstat -ano | findstr :22 # 输出示例 # TCP 0.0.0.0:22 0.0.0.0:0 LISTENING 12345 # 12345 就是进程 PID用它查进程名 tasklist | findstr 12345如果netstat没有输出或输出的 PID 对应的进程名不是sshd.exe说明sshd根本没绑定端口。此时回到日志重点排查sshd_config中的ListenAddress和Port设置。4.5 第五步防火墙放行——让 22 端口从“理论可用”变成“实际可达”sshd成功监听 22 端口后外部连接仍可能失败因为 Windows 防火墙默认阻止入站连接。必须为sshd.exe创建入站规则# 创建一条允许 sshd.exe 入站的规则 New-NetFirewallRule -Name OpenSSH-Server-In-TCP -DisplayName OpenSSH Server (sshd) -Enabled True -Direction Inbound -Protocol TCP -Action Allow -Program C:\Windows\System32\OpenSSH\sshd.exe -Profile Domain,Private这条命令的关键点-Program指向sshd.exe的绝对路径而非端口这样更安全避免其他程序占用 22 端口时被误放行-Profile Domain,Private表示仅在域网络和专用网络家庭/办公生效不开放公网Public网络符合最小权限原则验证规则是否生效# 查看刚创建的规则 Get-NetFirewallRule -DisplayName OpenSSH Server (sshd)5. 实战验证与进阶技巧从localhost连接到Navicat 17隧道的全链路打通当sshd终于稳定运行下一步是验证它是否真正可用。验证不能只停留在telnet localhost 22而要模拟真实业务场景——比如用 Navicat 17 通过 SSH 隧道连接数据库。5.1 基础连通性测试三步确认sshd健康本地回环测试确认服务在本机工作# 在同一台 Windows 上用 OpenSSH 客户端连接自己 ssh -o StrictHostKeyCheckingno -o UserKnownHostsFileNUL localhost如果成功进入 shell输入exit退出说明sshd本地服务正常。局域网测试确认网络可达 在另一台局域网内的 Windows 或 Mac 上执行ssh username192.168.1.100其中192.168.1.100是你的 Windows 服务器 IP。如果提示输入密码并成功登录说明网络和防火墙无阻塞。SFTP 测试确认文件子系统工作sftp username192.168.1.100 sftp ls sftp exit能列出远程家目录证明 SFTP 子系统已启用。5.2 Navicat 17 SSH 隧道配置绕过“Connection refused”的终极方案Navicat 17 连接 MySQL/PostgreSQL 时常因 SSH 隧道配置错误提示Connection refused。这不是sshd的问题而是 Navicat 的隧道参数与 Windows OpenSSH 的行为不匹配。关键配置如下Navicat 隧道设置项推荐值说明SSH Host192.168.1.100Windows 服务器的局域网 IP不是localhostPort22OpenSSH 默认端口确保与sshd_config一致Usernameyour_windows_username你登录 Windows 的用户名非rootAuthenticationPassword或Public Key若用密钥需将私钥转为 PuTTY 格式.ppkMySQL Host127.0.0.1关键这是隧道另一端的目标地址即数据库在 Windows 本机上的地址MySQL Port3306MySQL或5432PostgreSQL数据库服务端口注意MySQL Host必须填127.0.0.1而不是localhost。因为 Windows 的localhost解析可能走 IPv6::1而 MySQL 服务可能只监听 IPv4 的127.0.0.1导致连接失败。5.3 进阶技巧用ssh命令行实现自动化传输替代 GUI 工具很多用户习惯用 WinSCP 或 FileZilla但命令行scp和rsync更适合脚本化。Windows OpenSSH 客户端已内置scp# 从 Windows 上传文件到远程 Linux 服务器 scp C:\data\report.txt user192.168.1.200:/home/user/ # 从远程 Linux 下载文件到 Windows scp user192.168.1.200:/var/log/app.log C:\logs\ # 使用 rsync需在 Windows 上安装 rsync如通过 WSL 或 Cygwin # 但更推荐用 PowerShell 的 Copy-Item 配合 ssh 执行远程命令 Invoke-Command -ComputerName 192.168.1.200 -ScriptBlock { # 在远程 Linux 上执行命令如压缩日志 tar -czf /tmp/logs.tar.gz /var/log/*.log } -Credential (Get-Credential)5.4 长期维护建议一键启动/停止脚本与日志轮转为避免每次重启后手动net start创建一个start-sshd.ps1脚本# C:\scripts\start-sshd.ps1 # 以管理员身份运行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force # 确保依赖项就绪 if ((Get-Service ssh-agent).Status -ne Running) { net start ssh-agent } # 启动主服务 if ((Get-Service sshd).Status -ne Running) { net start sshd } # 验证端口监听 $portCheck netstat -ano | findstr :22 if ($portCheck) { Write-Host ✅ sshd 已成功启动监听 22 端口 -ForegroundColor Green } else { Write-Host ❌ sshd 启动失败请检查日志 -ForegroundColor Red }将其添加到 Windows 启动项或用任务计划程序在用户登录时触发。对于日志管理sshd默认不轮转。可在sshd_config中添加# 启用日志轮转需配合 Windows 任务计划 SyslogFacility LOCAL0 LogLevel INFO然后用 Windows 任务计划每天凌晨执行Remove-Item C:\ProgramData\ssh\logs\*.log.* -Force清理旧日志。我在实际运维中发现这套组合拳下来sshd的稳定性从最初的“三天两头挂”提升到“连续运行三个月无异常”。关键不在于多高深的技术而在于对 Windows 服务模型、OpenSSH 配置逻辑、以及 NTFS 权限体系的透彻理解。它不是一个“点一下就好的功能”而是一套需要你亲手调校的基础设施。当你真正掌握它你会发现Windows 作为 SSH 服务器其健壮性和灵活性远超大多数人的想象。
RELATED READING

延伸阅读

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