Introduction
WinRM (Windows Remote Management) is Ansible's default transport for Windows targets, but it requires opening ports 5985/5986 and managing certificates. An alternative approach tunnels WinRM through SSH using the PowerShell Remoting Protocol (PSRP) — combining WinRM's Windows-native capabilities with SSH's proven security model.
Why Tunnel WinRM via SSH?
| Approach | Ports Required | Encryption | Certificate Management |
|---|---|---|---|
| WinRM HTTP | 5985 | None | None |
| WinRM HTTPS | 5986 | TLS | Required |
| WinRM via SSH (PSRP) | 22 | SSH | SSH keys only |
| Native SSH | 22 | SSH | SSH keys only |
Benefits of SSH tunneling:
- Single port — only port 22 needed (often already allowed through firewalls)
- SSH key authentication — no certificate management
- Existing infrastructure — reuse SSH bastion hosts and jump servers
- Encryption — SSH provides proven end-to-end encryption
Prerequisites
On the Windows Target
1. Install OpenSSH Server
# Windows 10/11 and Server 2019+
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# Start and enable the SSH service
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
# Verify
Get-Service sshd
2. Install PowerShell 7+
# Install via winget
winget install Microsoft.PowerShell
# Or download from GitHub
# https://github.com/PowerShell/PowerShell/releases
3. Configure SSH for PowerShell Remoting
Edit C:\ProgramData\ssh\sshd_config:
# Add PowerShell subsystem
Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -nologo
# Enable password authentication (or use key-based)
PasswordAuthentication yes
PubkeyAuthentication yes
Restart SSH:
Restart-Service sshd
On the Ansible Controller
# Install the PSRP connection plugin
pip install pypsrp
# Or install with SSH transport support
pip install pypsrp[credssp,kerberos]
Ansible Configuration
Inventory with PSRP over SSH
[windows_ssh]
win01.example.com
win02.example.com
[windows_ssh:vars]
ansible_connection=psrp
ansible_psrp_protocol=http
ansible_psrp_proxy=socks5h://localhost:1080
ansible_user=ansible_admin
ansible_password="{{ vault_win_password }}"
SSH Tunnel Method
Option 1: Manual SSH Tunnel
# Create SSH tunnel from local port 5985 to Windows WinRM
ssh -L 5985:localhost:5985 ansible_admin@win01.example.com -N -f
Then configure Ansible to connect through localhost:
[windows_tunneled]
win01 ansible_host=127.0.0.1 ansible_port=5985
[windows_tunneled:vars]
ansible_connection=winrm
ansible_winrm_transport=basic
ansible_winrm_scheme=http
ansible_user=ansible_admin
ansible_password="{{ vault_win_password }}"
Option 2: Native SSH Connection (PowerShell 7+)
[windows_native_ssh]
win01.example.com
win02.example.com
[windows_native_ssh:vars]
ansible_connection=ssh
ansible_shell_type=powershell
ansible_user=ansible_admin
ansible_ssh_private_key_file=~/.ssh/windows_key
This uses native SSH without WinRM at all — the simplest approach when PowerShell 7 is available.
Option 3: PSRP with SSH Transport
[windows_psrp_ssh]
win01.example.com
[windows_psrp_ssh:vars]
ansible_connection=psrp
ansible_psrp_protocol=http
ansible_psrp_auth=negotiate
ansible_user=ansible_admin@DOMAIN
ansible_password="{{ vault_win_password }}"
Connection Method Comparison
| Method | Connection Plugin | Requires | Best For |
|---|---|---|---|
| WinRM HTTPS | winrm | Port 5986 + certs | Standard Windows automation |
| SSH + PowerShell | ssh | OpenSSH + PS7 | Simple SSH-only environments |
| PSRP direct | psrp | Port 5985/5986 | High-performance WinRM |
| SSH tunnel + WinRM | winrm | SSH tunnel | Restrictive firewalls |
| PSRP over SSH | psrp | SSH + PSRP | Maximum flexibility |
Test Connectivity
---
- name: Test Windows SSH connectivity
hosts: windows_native_ssh
tasks:
- name: Ping via SSH
ansible.windows.win_ping:
- name: Run PowerShell command
ansible.windows.win_shell: |
$PSVersionTable.PSVersion
hostname
register: ps_output
- name: Show result
ansible.builtin.debug:
var: ps_output.stdout_lines
ansible-playbook test_win_ssh.yml -i inventory.ini
SSH Key Authentication
Deploy Keys to Windows
# Copy public key to Windows authorized_keys
ssh-copy-id ansible_admin@win01.example.com
# Or manually add to Windows
# C:\Users\ansible_admin\.ssh\authorized_keys
# C:\ProgramData\ssh\administrators_authorized_keys (for admin users)
Fix Permissions for Admin Users
On Windows, admin user keys require a special file:
# Create administrators authorized keys file
$authKeys = "C:\ProgramData\ssh\administrators_authorized_keys"
Add-Content $authKeys "ssh-ed25519 AAAA... ansible@controller"
# Fix permissions (required!)
icacls $authKeys /inheritance:r /grant "SYSTEM:F" /grant "BUILTIN\Administrators:F"
Through a Bastion/Jump Host
[windows_via_bastion]
win01.internal ansible_host=10.0.1.10
[windows_via_bastion:vars]
ansible_connection=ssh
ansible_shell_type=powershell
ansible_user=ansible_admin
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'
Troubleshooting
"Connection Refused" on Port 22
# Verify SSH is running
Get-Service sshd
# Check firewall
Get-NetFirewallRule -Name *ssh*
# Add firewall rule if missing
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server' `
-Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
PowerShell Subsystem Not Found
no matching subsystem found
Verify sshd_config has the subsystem line and the path is correct:
# Find PowerShell 7 path
(Get-Command pwsh).Source
# Should be: C:\Program Files\PowerShell\7\pwsh.exe
Permission Denied
# Test SSH manually first
ssh -v ansible_admin@win01.example.com
# Check Windows Event Viewer for auth errors
# Applications and Services Logs → OpenSSH → Operational
Related Articles
- Ansible for Windows Guide
- Configure Windows Host for Ansible (WinRM)
- Test Windows: win_ping Module
- Install Ansible on Windows WSL
Conclusion
Tunneling WinRM via SSH eliminates certificate management and reduces firewall exposure to a single port. For the simplest setup, use native SSH with PowerShell 7 (ansible_connection=ssh + ansible_shell_type=powershell). For environments requiring WinRM features, create SSH tunnels and point Ansible at localhost. Always use SSH key authentication over passwords, and leverage bastion hosts for internal Windows servers.