OpenSSH Server on Windows Server 2019

Windows Server 2019 ships with OpenSSH as an optional Windows capability. Installing the OpenSSH Server component provides a native SSH daemon (sshd) that allows Linux administrators, developers, and automation tools to manage Windows servers using the same SSH workflow they use for Linux—including key-based authentication, SFTP file transfer, and tunneling. This eliminates the need for PuTTY-specific configurations and enables tools like Ansible to use SSH instead of WinRM for Windows automation.

Installing OpenSSH Server


# Check available OpenSSH capabilities
Get-WindowsCapability -Online -Name OpenSSH*

# Install OpenSSH Server and Client
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0

# Verify installation
Get-WindowsCapability -Online -Name OpenSSH* | Select-Object Name, State

# Start and configure the SSH service
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic

# Verify sshd is listening on port 22
Get-NetTCPConnection -LocalPort 22 | Select-Object LocalAddress, LocalPort, State

Configuring the Firewall


# The OpenSSH installer creates this rule automatically; verify it exists
Get-NetFirewallRule -DisplayName 'OpenSSH Server (sshd)'

# If the rule is missing, create it manually
New-NetFirewallRule -DisplayName 'OpenSSH Server (sshd)' `
    -Direction Inbound -Protocol TCP -LocalPort 22 `
    -Action Allow -Profile Domain,Private `
    -RemoteAddress '10.0.0.0/8','192.168.0.0/16'  # restrict to internal only

# For production: restrict source IPs to management networks only
Set-NetFirewallRule -DisplayName 'OpenSSH Server (sshd)' `
    -RemoteAddress '10.0.100.0/24'  # management VLAN only

Configuring sshd_config

The SSH daemon configuration file is at C:ProgramDatasshsshd_config. Edit it to harden the server:


# View the default sshd_config
Get-Content 'C:ProgramDatasshsshd_config'

# Edit with a text editor or via PowerShell
$sshd_config = 'C:ProgramDatasshsshd_config'

# Key hardening settings to apply:
$hardeningLines = @'
# Disable password authentication (use keys only after deploying keys)
PasswordAuthentication no
# Disable empty passwords
PermitEmptyPasswords no
# Disable root/Administrator login (use specific admin accounts)
PermitRootLogin no
# Restrict to SSH protocol 2 only
Protocol 2
# Disable TCP forwarding if not needed
AllowTcpForwarding no
# Disable X11 forwarding
X11Forwarding no
# Set login timeout
LoginGraceTime 30
# Maximum authentication attempts
MaxAuthTries 3
# Log level for audit
LogLevel VERBOSE
# Restrict allowed users to specific group
AllowGroups "SSH-Users" Administrators
'@

# Apply changes
Add-Content $sshd_config $hardeningLines

# Restart sshd to apply
Restart-Service sshd

Setting Up Key-Based Authentication

SSH key-based authentication is far more secure than password authentication. Windows Server 2019’s OpenSSH implementation supports both standard authorized_keys files (for regular users) and an administrators_authorized_keys file for accounts in the Administrators group:


# For regular domain users - authorized_keys in user's profile
# C:Usersusername.sshauthorized_keys

# Create the .ssh directory for a user
$username  = 'jsmith'
$sshDir    = "C:Users$username.ssh"
$authKeys  = "$sshDirauthorized_keys"

New-Item -Path $sshDir -ItemType Directory -Force
New-Item -Path $authKeys -ItemType File -Force

# Set correct permissions (critical - sshd rejects keys in world-readable files)
icacls $authKeys /inheritance:r
icacls $authKeys /grant "${username}:(R)"
icacls $authKeys /grant "SYSTEM:(F)"

# Add the user's public key
$pubKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... jsmith@linux-workstation'
Set-Content $authKeys $pubKey

# For Administrator accounts: use the special file
$adminKeys = 'C:ProgramDatasshadministrators_authorized_keys'
New-Item -Path $adminKeys -ItemType File -Force

# Set strict permissions - only SYSTEM and Administrators can read this file
icacls $adminKeys /inheritance:r
icacls $adminKeys /grant "SYSTEM:(F)"
icacls $adminKeys /grant "BUILTINAdministrators:(F)"

# Add admin public key
Add-Content $adminKeys 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... admin@ansible-controller'

# Verify permissions (sshd will reject keys if permissions are too open)
Get-Acl $adminKeys | Format-List

Generating and Deploying Keys from a Linux Client


# On the Linux/WSL client, generate a 4096-bit RSA key pair
ssh-keygen -t rsa -b 4096 -C "admin@ansible-controller" -f ~/.ssh/windows_server_key

# Copy the public key to the Windows server
# (ssh-copy-id doesn't work directly; use PowerShell remoting or manual copy)
cat ~/.ssh/windows_server_key.pub | ssh [email protected] `
    'powershell -Command "Add-Content C:ProgramDatasshadministrators_authorized_keys ([Console]::In.ReadToEnd())"'

# Test key-based login
ssh -i ~/.ssh/windows_server_key -p 22 [email protected]

# Disable password authentication after confirming key auth works
# Edit sshd_config: PasswordAuthentication no
# Restart-Service sshd

Configuring the Default Shell for SSH Sessions

By default, SSH sessions on Windows use cmd.exe. Change the default shell to PowerShell for a more capable interactive and scripting environment:


# Set PowerShell 5.1 as the default SSH shell
New-ItemProperty -Path 'HKLM:SOFTWAREOpenSSH' `
    -Name DefaultShell `
    -Value 'C:WindowsSystem32WindowsPowerShellv1.0powershell.exe' `
    -PropertyType String -Force

# Optionally set PowerShell 7 if installed
New-ItemProperty -Path 'HKLM:SOFTWAREOpenSSH' `
    -Name DefaultShell `
    -Value 'C:Program FilesPowerShell7pwsh.exe' `
    -PropertyType String -Force

# Verify
Get-ItemProperty 'HKLM:SOFTWAREOpenSSH' -Name DefaultShell

# Test that a new SSH session gets PowerShell
# ssh [email protected]
# $PSVersionTable  # should show PowerShell version

Configuring Ansible to Use SSH Instead of WinRM

With OpenSSH server installed and keys deployed, Ansible can manage Windows Server 2019 nodes via SSH using the ssh connection plugin:


# ansible inventory for SSH-based Windows management
# inventory/windows_ssh.yml
---
windows_ssh:
  hosts:
    srv01.corp.local:
  vars:
    ansible_connection: ssh
    ansible_shell_type: powershell
    ansible_user: adminuser
    ansible_ssh_private_key_file: ~/.ssh/windows_server_key
    ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'

# Test connectivity
# ansible srv01.corp.local -m win_ping

# Run an ad-hoc command
# ansible srv01.corp.local -m win_command -a 'Get-Process | Select-Object -First 5'

SFTP Access and File Transfer


# OpenSSH on Windows Server 2019 includes the SFTP subsystem by default
# Verify sftp-server path in sshd_config
Select-String -Path 'C:ProgramDatasshsshd_config' -Pattern 'Subsystem'
# Should show: Subsystem sftp C:WindowsSystem32OpenSSHsftp-server.exe

# Test SFTP from a Linux client
# sftp -i ~/.ssh/windows_server_key [email protected]

# Restrict a user to SFTP only (no shell)
# Add to sshd_config:
# Match User sftponly
#     ForceCommand internal-sftp
#     ChrootDirectory C:SFTP%u
#     AllowTcpForwarding no
#     X11Forwarding no

# Create chroot directory for SFTP-only user
New-Item -Path 'C:SFTPsftponly' -ItemType Directory -Force
icacls 'C:SFTPsftponly' /grant 'sftponly:(OI)(CI)(M)'

Auditing SSH Access


# SSH logs go to the Application event log under source OpenSSH
Get-WinEvent -LogName Application |
    Where-Object { $_.ProviderName -eq 'OpenSSH' } |
    Select-Object TimeCreated, Id, Message | Format-Table -Wrap

# For verbose logging, check:
# C:ProgramDatasshlogssshd.log  (if LogLevel VERBOSE set)

# Monitor for authentication failures
Get-WinEvent -LogName Application |
    Where-Object { $_.ProviderName -eq 'OpenSSH' -and $_.Message -like '*Failed password*' } |
    Select-Object TimeCreated, Message

# Set sshd log file
# In sshd_config:
# SyslogFacility LOCAL0
# LogLevel VERBOSE
# After restart, logs appear in C:ProgramDatasshlogssshd.log

Conclusion

OpenSSH Server on Windows Server 2019 transforms Windows administration by providing a native SSH interface that integrates seamlessly with Linux toolchains, Ansible automation, and DevOps pipelines. Key-based authentication enforced through administrators_authorized_keys, a PowerShell default shell, and hardened sshd_config settings make it a production-ready remote access method that complements or replaces WinRM depending on the operational requirements of the environment.