How To

Configure OpenSSH Server on Windows Server 2025

OpenSSH Server ships preinstalled on Windows Server 2025, yet a fresh box still refuses the first connection. On our test VM the sshd service sat at Stopped and Manual, and the OpenSSH-Server-In-TCP firewall rule was scoped to the Private profile only. The network adapter was on a Public network, so the client hit its 15-second ConnectTimeout until we widened the rule.

Original content from computingforgeeks.com - post 136216

Below we configure the OpenSSH server on Windows Server 2025 and 2022, from Add-WindowsCapability where it is missing to starting sshd and opening port 22. Then come key login through administrators_authorized_keys, PowerShell as the default SSH shell, and password logins switched off. The outputs come from two Proxmox VMs and an Ubuntu client, and the 2025 box ran OpenSSH 9.5p2 (tested October 2026 on Windows Server 2025 Datacenter, build 26100).

OpenSSH on Windows Server 2025 vs 2022

The two releases ship different OpenSSH builds and different defaults, and most of the steps below exist because of one row in this table. We read every value from the lab servers after a clean install of the capability on each.

Measured on the lab VMWindows Server 2025Windows Server 2022
sshd -VOpenSSH_for_Windows_9.5p2, LibreSSL 3.8.2OpenSSH_for_Windows_8.1p1, LibreSSL 3.0.2
OpenSSH.Server capability on a new installInstalledNotPresent
Add-WindowsCapability time1,439 s (re-added after removal, 275 MB from Windows Update)86.8 s
sshd service after installStopped, ManualStopped, Manual
OpenSSH-Server-In-TCP profilePrivateAny
AllowGroups in the default sshd_configadministrators "openssh users"Not set
Local group OpenSSH UsersPresentAbsent
Admin key file with inherited ACL (Authenticated Users read)AcceptedRefused
Key exchange with an OpenSSH 10.2 clientcurve25519-sha256, post-quantum warningcurve25519-sha256, post-quantum warning

Three rows change the procedure, and each one maps to a step below.

  • Firewall profile (Step 4): the Private-only rule on Server 2025 blocks port 22 on any server whose network is Public or Domain, and our lab server sat on a Public network.
  • AllowGroups (Step 6): Server 2025 rejects any user who is neither an administrator nor in OpenSSH Users, even with the right password.
  • Admin key ACL (Step 8): Server 2022 refuses a key file that Server 2025 accepts, so one icacls command belongs in every setup.

Server 2022 also needs the 86.8 second download first, and it runs an OpenSSH release from 2019. The last row applies to both releases. OpenSSH 10.1 and newer clients print a three-line warning on every connection, because neither Windows build negotiates a post-quantum key exchange, and Step 5 shows what that means in practice.

Step 1: Check whether OpenSSH Server is installed

The capability state decides whether Step 2 applies to your server, so read it first. Open PowerShell with Run as administrator, because every server-side command in this guide changes or reads system state, and list both OpenSSH capabilities:

Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'

Our Server 2022 VM reported the client present and the server missing, which is the normal state for that release.

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

On Server 2025 both lines read Installed, and Step 2 can be skipped. Installed does not mean reachable, though. Check the service, the firewall rule and the network category in the same window:

Get-Service sshd | Select-Object Name, Status, StartType
Get-NetFirewallRule -Name OpenSSH-Server-In-TCP | Select-Object Name, Enabled, Profile
Get-NetConnectionProfile | Select-Object Name, NetworkCategory

A new Server 2025 install leaves three problems in this output. The service is stopped, it will not start at boot, and the only inbound rule applies to a network category the adapter is not on.

Name  Status StartType
----  ------ ---------
sshd Stopped    Manual

Name                  Enabled Profile
----                  ------- -------
OpenSSH-Server-In-TCP    True Private

Name    NetworkCategory
----    ---------------
Network          Public

Windows files a network it cannot identify as Public, and our VM, fresh from a standard Windows Server 2025 install with no domain, landed there. Steps 3 and 4 fix the service and the rule; the network category itself can stay as it is.

Step 2: Install OpenSSH Server with Add-WindowsCapability

Server 2022 needs this step, and so does a 2025 server where someone removed the capability. The package is a Feature on Demand served by Windows Update, so the server needs outbound HTTPS to Microsoft, or a WSUS or Feature on Demand source that carries it. Run the install from the elevated window:

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

Nothing prints during the download, and then a short object comes back. RestartNeeded : False is the line to look for.

Path          :
Online        : True
RestartNeeded : False

Our 2022 VM finished in 86.8 seconds. Re-adding the capability on the 2025 VM took 1,439 seconds, because Windows Update first pulled a 275 MB package for the build, so a removed capability on Server 2025 costs about 24 minutes to restore. Neither server asked for a reboot. The installer created the OpenSSH-Server-In-TCP rule and registered sshd as a Manual service, but nothing listens on port 22 until Step 3.

Step 3: Start sshd and set it to start at boot

Two cmdlets do it: one sets the startup type so the service survives a reboot, the other starts it now. The first start also generates the host keys and writes C:\ProgramData\ssh\sshd_config from the default template, which is why that folder does not exist until this point.

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

Confirm the service state and the listening sockets. sshd binds port 22 on all IPv4 and IPv6 addresses.

Get-Service sshd | Select-Object Name, Status, StartType
Get-NetTCPConnection -LocalPort 22 -State Listen | Select-Object LocalAddress, LocalPort, State

Running plus Automatic on the first table, and two Listen entries on the second, is the healthy result.

Name  Status StartType
----  ------ ---------
sshd Running Automatic

LocalAddress LocalPort  State
------------ ---------  -----
::                  22 Listen
0.0.0.0             22 Listen

Server 2025 also exposes the same switch in Server Manager, which Microsoft’s OpenSSH getting-started page documents alongside the note that OpenSSH is now installed by default. Under Local Server, the Properties pane has a Remote SSH Access row that reads Disabled on a new install:

Server Manager Local Server properties on Windows Server 2025 with Remote SSH Access set to Disabled

Clicking Disabled does not open a dialog. It opens an elevated PowerShell console with a confirmation prompt, and the prompt text states the defaults more plainly than the documentation does: SSH is only available on a private network, and only local administrators and members of OpenSSH Users may log in.

PowerShell prompt opened by Remote SSH Access on Windows Server 2025 stating SSH is only available on a private network by default

Both choices show as [] Yes [] No with empty hotkeys, so typing Y just reprinted the prompt on our VM. Press Enter to accept the default of Yes. That set sshd to Running and Automatic, exactly as the two cmdlets above do, and left the firewall rule on Private. The Local Server page kept showing Disabled until it was refreshed with F5.

Step 4: Open port 22 for every firewall profile

With sshd running and the rule still on Private, the first connection from our Ubuntu client went nowhere. The packets were dropped rather than rejected, so the client waited for its full timeout.

ssh -o ConnectTimeout=15 [email protected] whoami

After exactly 15.0 seconds:

ssh: connect to host 10.0.1.50 port 22: Connection timed out

Extend the existing rule to all three profiles instead of creating a second rule. A duplicate rule works too, but it leaves two entries to audit and the in-box one still looks correct at a glance.

Set-NetFirewallRule -Name OpenSSH-Server-In-TCP -Profile Domain,Private,Public
Get-NetFirewallRule -Name OpenSSH-Server-In-TCP | Select-Object Name, Enabled, Profile

All three categories now appear in the Profile column.

Name                  Enabled                 Profile
----                  -------                 -------
OpenSSH-Server-In-TCP    True Domain, Private, Public

On a server that faces the internet, add -RemoteAddress 10.0.1.0/24 (your admin subnet) to the same Set-NetFirewallRule call, so port 22 answers only the hosts that should manage the box. Our guide to opening a port in Windows Server Firewall covers scoping, listing and removing rules in more detail. Server 2022 needs no change here, since its rule is created with the Any profile.

Step 5: Log in with a password and check the shell

Test with a local administrator first, because that account is allowed by the default configuration on both releases. Local accounts work as a plain user name; a domain account takes the domain\user form, quoted or escaped in a Linux shell. Our lab admin is klinsmann, created as in our guide to adding a local user on Windows Server:

ssh [email protected]

After the host key prompt and the password, the session lands in cmd.exe, the default shell on both releases:

Microsoft Windows [Version 10.0.26100.32230]
(c) Microsoft Corporation. All rights reserved.

klinsmann@BENCH01 C:\Users\klinsmann>

From an OpenSSH 10.1 or newer client, three lines also print before the banner on every connection, to both Server 2025 and Server 2022:

** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html

The session is still encrypted. ssh -v showed kex: algorithm: curve25519-sha256 against both servers, which means the Windows builds offered no hybrid post-quantum method for the client to pick. The real fix is a Windows OpenSSH build that negotiates one, so treat the warning as information, not as a misconfiguration on your side. To silence it for these hosts only, -o WarnWeakCrypto=no (or the same option in a Host block of your SSH client config) removed all three lines in our test.

Step 6: Allow standard users through the OpenSSH Users group

Server 2025 writes one line into its default sshd_config that Server 2022 does not have, and it decides who may log in at all:

AllowGroups administrators "openssh users"

To test it we created a second local account, deploy, with New-LocalUser and left it out of Administrators. That standard user typed the correct password three times and got Permission denied (publickey,password,keyboard-interactive). The client gives no hint that the password was right. The server log does, and it is the first place to look when one account works and another does not.

Get-WinEvent -LogName 'OpenSSH/Operational' -MaxEvents 5 | Select-Object TimeCreated, Message

sshd logs the refusal before it even checks the password, and then labels the account an invalid user.

sshd: User deploy from 10.0.1.20 not allowed because none of user's groups are listed in AllowGroups
sshd: Failed password for invalid user deploy from 10.0.1.20 port 35536 ssh2

Add the account to the local group that the default configuration already trusts. Group membership is read at login, so neither a restart of sshd nor a new session on the server is needed.

Add-LocalGroupMember -Group 'OpenSSH Users' -Member deploy

The next attempt from the client returned bench01\deploy. We would keep this model instead of deleting the AllowGroups line: SSH access becomes a group membership that shows up in any audit of local groups, and a service account that never needed a shell stays locked out by default.

Step 7: Set up key login for a standard user

Generate a key pair on the client if it has none. Ed25519 is the default type in current OpenSSH and the one we tested:

ssh-keygen -t ed25519

For a standard user, sshd reads C:\Users\<user>\.ssh\authorized_keys, the same relative path as on Linux. Create the folder and copy the key with two commands, both authenticated by password this one time. The mkdir runs in cmd.exe on the server and prints nothing:

ssh [email protected] "mkdir .ssh"
scp ~/.ssh/id_ed25519.pub [email protected]:.ssh/authorized_keys

Test with BatchMode so the client fails instead of falling back to a password prompt. That makes a broken key setup obvious.

ssh -o BatchMode=yes [email protected] whoami

Back comes the account name, with no password prompt in between.

bench01\deploy

The scp above overwrites any existing authorized_keys. For a second key, append it from a session on the server instead, for example with Add-Content as shown for administrators in the next step.

Step 8: Use administrators_authorized_keys for admin accounts

Repeating Step 7 for klinsmann produced a key file that sshd never read. The batch test failed with Permission denied (publickey,password,keyboard-interactive) on both releases, and the Server 2022 log recorded nothing more useful than a closed connection. The cause is this block at the end of the default sshd_config:

Match Group administrators
       AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys

For any member of the local Administrators group, sshd ignores the profile copy and reads one shared file in C:\ProgramData\ssh. Every key in that file can log in as every administrator on the box, so keep it short and remove keys when people leave. Append the public key on the server from an elevated PowerShell window, pasting the full ssh-ed25519 ... line from the client’s id_ed25519.pub:

Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIITzf3azp2JHDzNkyCSnLHw6/vwBQ7vh0dfEr13WMLrX klinsmann@bench01' -Encoding ascii

Then lock the file down to SYSTEM and Administrators. This is the command from Microsoft’s OpenSSH Server configuration reference, followed by a check of the result.

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

Exactly two entries must remain. Server 2022 refuses the file over any other entry, and Server 2025 refuses one that grants write access, such as BUILTIN\Users with Modify.

C:\ProgramData\ssh\administrators_authorized_keys NT AUTHORITY\SYSTEM:(F)
                                                  BUILTIN\Administrators:(F)

Successfully processed 1 files; Failed processing 0 files

Each release enforces this differently, which is why a setup that works on one can fail on the other. Server 2025 accepted the file with its inherited ACL, which grants Authenticated Users read access. Server 2022 refused the same ACL and logged only sshd: Authentication refused. Run the icacls line on both and the difference never matters. With the ACL fixed, the admin key login works:

ssh -o BatchMode=yes [email protected] whoami

sshd answers with the account and logs the key fingerprint it matched.

bench01\klinsmann

In the OpenSSH/Operational log, the same login appears as Accepted publickey for klinsmann from 10.0.1.20 port 59092 ssh2: ED25519 SHA256:zUQOL36jViDv2y7Ww7vqsgjrFyXJqQV223gDF7ta7o4, which is how you tell which of several keys in the shared file was used.

Step 9: Make PowerShell the default SSH shell

Windows keeps the default SSH shell in the registry, not in sshd_config. The value takes the full path of the executable, and it applies to the server only, never to the SSH client on the same machine.

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

PowerShell echoes the value it wrote, with the provider path underneath.

DefaultShell : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\OpenSSH

sshd reads the value for each new session, so the next login opened Windows PowerShell 5.1 without a service restart. Commands passed on the ssh command line now run in PowerShell as well, which matters for scripts written against cmd.exe syntax. In return, a one-liner such as ssh [email protected] '$PSVersionTable.PSVersion; Get-Service sshd' returns PowerShell objects directly, as the capture in Step 10 shows. If PowerShell 7 is installed, point the value at C:\Program Files\PowerShell\7\pwsh.exe instead; we only tested the in-box 5.1 build.

Step 10: Turn off password authentication

Do this last, and only after Step 8 works for at least one administrator, because a mistake here locks out every remote login. Keep a console or RDP session open while you test. Open the server configuration in Notepad from the elevated window:

notepad C:\ProgramData\ssh\sshd_config

Find the commented #PasswordAuthentication yes line and replace it with:

PasswordAuthentication no

sshd reads its configuration only at startup. Validate the file before restarting, because a typo stops the service from starting and closes the door completely. sshd.exe -t prints nothing when the file is valid:

& "$env:WINDIR\System32\OpenSSH\sshd.exe" -t
Restart-Service sshd

From the client, force a password-only attempt by switching public key authentication off for one command.

ssh -o PubkeyAuthentication=no [email protected] whoami

The server refuses it without asking for a password, and password has dropped out of the list of methods it offers.

[email protected]: Permission denied (publickey,keyboard-interactive).

Key login is unaffected. The capture below repeats both tests and runs a PowerShell one-liner over the key session; the post-quantum warning printed on all three connections, and we left it in for the first one only to keep the window readable.

Linux terminal showing password login refused and SSH key login accepted on Windows Server 2025 with PowerShell as the default shell

Windows still lists keyboard-interactive, but our password attempts through that method were refused as well. If you also run Ansible against Windows Server, Ansible can also reach Windows over SSH instead of WinRM; we did not test that path in this lab.

Troubleshooting OpenSSH Server on Windows

Every error below came from the two lab servers. Most of them read the same on the client, so the server log in OpenSSH/Operational is the tool that separates them.

ssh: connect to host 10.0.1.50 port 22: Connection timed out

A timeout, rather than Connection refused, means a firewall dropped the packets. On Server 2025 the in-box rule covers only the Private profile, so check Get-NetConnectionProfile and widen the rule as in Step 4. Connection refused instead means the packets arrived and nothing listened, which points back to a stopped sshd service.

not allowed because none of user’s groups are listed in AllowGroups

This server-side message explains a client-side Permission denied for a user who typed the right password. Add the account to OpenSSH Users (Step 6), or to a group you list in AllowGroups yourself. Group names in that directive must be written in lower case, and a name with a space needs double quotes, as in the default line.

Bad permissions. Try removing permissions for user: BUILTIN\\Users

Server 2025 logged this, followed by Authentication refused., after we granted BUILTIN\Users Modify on the admin key file:

sshd: Bad permissions. Try removing permissions for user: BUILTIN\\Users (S-1-5-32-545) on file C:/ProgramData/ssh/administrators_authorized_keys.
sshd: Authentication refused.

The Microsoft icacls ... /inheritance:r line from Step 8 did not fix it. It strips inherited entries only, and this one was explicit, so the listing afterwards still showed BUILTIN\Users:(M). Remove the named account directly, then list the ACL again:

icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /remove "Users"

Explicit entries like this usually arrive when the file is created elsewhere and moved into place, or edited with a tool that rewrites its security descriptor. Server 2022 printed only Authentication refused. for the same class of problem, so check the ACL first whenever an admin key fails there.

Permission denied (publickey,password,keyboard-interactive) for an administrator with a valid key

The key is almost certainly in C:\Users\<user>\.ssh\authorized_keys, which sshd skips for members of Administrators. Move it into administrators_authorized_keys as in Step 8. On Server 2022 it left no explanation in the log, only Connection closed by authenticating user ... [preauth], so it is easy to lose an hour on.

What to watch in the OpenSSH log

Windows writes sshd events to the OpenSSH/Operational channel in Event Viewer, under Applications and Services Logs, OpenSSH. We pulled the last 12 entries after the final key login, and the healthy pattern was the same on every connection: a Server listening on 0.0.0.0 port 22. pair after each restart, then Accepted publickey, Received disconnect and Disconnected for each session. A short list of lines deserves an alert:

  • Failed password for invalid user: after Step 10 these should stop entirely. A steady stream means port 22 is reachable from somewhere it should not be, so scope the firewall rule with -RemoteAddress.
  • not allowed because none of user’s groups are listed in AllowGroups: a real person missing from OpenSSH Users, or a scanner trying common names. The user name tells you which.
  • Bad permissions or Authentication refused.: someone edited administrators_authorized_keys or the .ssh folder of a user. Re-run the ACL check from Step 8.
  • Accepted publickey with an unknown SHA256 fingerprint: a key in the shared admin file that nobody can name. Match it against ssh-keygen -lf output from your team’s public keys, and remove it if it has no owner.

Over the new key login, the same channel can be read remotely, which is the practical payoff of Step 9. Running Get-WinEvent -LogName 'OpenSSH/Operational' -MaxEvents 20 from an SSH session returns the table directly, without an RDP session or Event Viewer. On a fleet, forward the channel to your SIEM so these four patterns are counted rather than read by eye.

Keep reading

UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Configure NTP Server on Windows Server 2025 / 2022 (w32tm Guide) Windows Configure NTP Server on Windows Server 2025 / 2022 (w32tm Guide) Windows 11 Keyboard Shortcuts Cheat Sheet (Printable PDF) Cheat Sheets Windows 11 Keyboard Shortcuts Cheat Sheet (Printable PDF) Best CRISC Books and Study Guides for the 2025 Job Practice Books Best CRISC Books and Study Guides for the 2025 Job Practice

Leave a Comment

Press ESC to close