Classic Windows PowerShell remoting (WinRM/WSMan) is great inside an Active Directory forest โ and a nightmare anywhere else. Cross-domain, cross-OS, or out-of-AD scenarios mean firewall holes, certificate work, and HTTP listeners that nobody wants to maintain. PowerShell 7 fixes this by adding SSH as a first-class remoting transport. Same Enter-PSSession, same Invoke-Command โ just over a TCP/22 SSH tunnel that already works in every environment.
This guide walks through both sides of the setup (Windows host running OpenSSH Server, Linux host running OpenSSH Server with PowerShell 7), key-based authentication, jump hosts, common errors, and the patterns you actually use day-to-day. Free PDF cheat sheet at the bottom.
Table of Contents
- Why SSH instead of WinRM?
- Install PowerShell 7 on both ends
- Install OpenSSH Server on Windows
- Configure OpenSSH on Linux for PowerShell
- Update sshd_config (the critical step)
- Test the connection
- Key-based authentication
- Persistent sessions and Enter-PSSession
- Jump hosts
- Common errors
- Cheat sheet
- FAQ
Why SSH instead of WinRM?
WinRM is excellent inside a single Active Directory forest. Outside it: cross-OS does not work, cross-domain needs trust or CredSSP, opening port 5985/5986 inbound on a remote network is rarely allowed, and certificate-based listener setup has too many moving parts. SSH solves all of this. It works on every modern OS, the firewall rule (TCP/22) is usually already there, key-based auth is well understood, and you can chain through bastion hosts without changing anything in PowerShell.
The trade-off: SSH-based remoting in PowerShell 7 does not support implicit Kerberos / domain auth (you handle auth at the SSH layer), it does not currently support disconnected sessions (you need an active SSH connection), and a few WinRM-specific configuration cmdlets do not apply. For every other case, SSH is the better choice in 2026.
Install PowerShell 7 on both ends
You need PowerShell 7 on both sides โ the client and the server. SSH-based remoting calls pwsh on the remote, not powershell.exe. On Windows install the MSI from github.com/PowerShell/PowerShell/releases or via winget:
winget install --id Microsoft.PowerShell --source winget
On Debian/Ubuntu:
sudo apt update
sudo apt install -y wget apt-transport-https software-properties-common
source /etc/os-release
wget -q https://packages.microsoft.com/config/$ID/$VERSION_ID/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt update
sudo apt install -y powershell
pwsh --version
Install OpenSSH Server on Windows
OpenSSH Server is shipped as an optional Windows feature on Windows 10/11 and Server 2019+. Install and start it:
# Install OpenSSH Server
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# Start and set automatic
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
# Open the firewall (Windows usually does this automatically; verify)
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -DisplayName "OpenSSH Server (sshd)" `
-Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
}
Configure OpenSSH on Linux for PowerShell
OpenSSH Server is already installed on every server Linux. You only need to make sure pwsh is on the path and the SSH daemon knows about a PowerShell subsystem (next section).
Update sshd_config (the critical step)
This is the step everyone forgets. SSH-based PowerShell remoting works by asking the SSH daemon to launch a named subsystem called powershell. Add the line to sshd_config on every host that should accept inbound PowerShell sessions.
On Windows, edit C:\ProgramData\ssh\sshd_config:
Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -nologo
On Linux, edit /etc/ssh/sshd_config:
Subsystem powershell /usr/bin/pwsh -sshs -nologo
Note the path uses the 8.3 short name on Windows because sshd_config does not handle spaces in paths well. -sshs tells pwsh to run as a remoting server, -nologo suppresses the banner.
Restart the SSH daemon:
# Windows
Restart-Service sshd
# Linux
sudo systemctl restart ssh # or sshd, depending on the distro
Test the connection
# From a PowerShell 7 client
Enter-PSSession -HostName remote-server.example.com -UserName admin
# Or run a one-shot command
Invoke-Command -HostName web01,web02,web03 -UserName admin -ScriptBlock {
[PSCustomObject]@{
Name = $env:COMPUTERNAME
Up = (Get-Uptime).Days
}
}
The first time, SSH prompts you to accept the host key. After that, you get a remote prompt that looks just like classic PowerShell remoting:
[remote-server.example.com]: PS C:\Users\admin>
Key-based authentication
Password auth works but is slow and does not script well. Set up key auth once and forget it:
# 1. Generate a key on the client (if you do not already have one)
ssh-keygen -t ed25519 -C "psremoting@workstation"
# 2. Copy the public key to the remote
# Linux remote: use ssh-copy-id (the easy way)
ssh-copy-id admin@remote-server.example.com
# Windows remote: append to authorized_keys manually
# Note: for admin users, the file is C:\ProgramData\ssh\administrators_authorized_keys
# Permissions are strict - the file must be owned by SYSTEM or the admin group
The Windows admin file is the trap. Run as administrator and tighten ACLs:
$path = "C:\ProgramData\ssh\administrators_authorized_keys"
icacls.exe $path /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
Persistent sessions and Enter-PSSession
Once SSH is set up, the rest of the remoting cmdlets work as before:
# Create a persistent session
$s = New-PSSession -HostName web01 -UserName admin -KeyFilePath ~/.ssh/id_ed25519
# Run multiple commands in the same session
Invoke-Command -Session $s -ScriptBlock { Get-Service | Where-Object Status -eq 'Running' }
Invoke-Command -Session $s -ScriptBlock { Get-Process | Sort-Object CPU -Desc | Select-Object -First 5 }
# Enter interactively
Enter-PSSession -Session $s
# ... do work ...
Exit-PSSession
# Clean up
Remove-PSSession $s
Jump hosts
One of the biggest wins of SSH remoting: bastion-style jump hosts. Configure them in ~/.ssh/config and PowerShell uses them transparently:
Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519
Host internal-*
User admin
IdentityFile ~/.ssh/id_ed25519
ProxyJump bastion
# Routes through bastion automatically
Enter-PSSession -HostName internal-app01.local -UserName admin
Common errors
"No subsystem powershell" / "Could not negotiate session"
The Subsystem powershell ... line is missing from sshd_config on the remote, or the SSH daemon was not restarted. Verify with grep -i subsystem /etc/ssh/sshd_config on Linux or Get-Content C:\ProgramData\ssh\sshd_config | Select-String subsystem on Windows.
"pwsh: command not found"
PowerShell 7 is not installed on the remote, or the path in the Subsystem line is wrong. which pwsh on the remote should print a path; that is the path to put in sshd_config.
Connection times out on Windows remote
The Windows firewall is blocking TCP/22. Add the rule shown above. If sshd is running on a non-default port, pass -Port to Enter-PSSession.
Permission denied on Windows admin user, despite key
The key has to be in C:\ProgramData\ssh\administrators_authorized_keys (not the user's .ssh\authorized_keys) when the user is an admin, and ACLs must be locked down. Check the sshd log at C:\ProgramData\ssh\logs\sshd.log.
Cheat sheet
The full setup, key copy commands, and config snippets in one printable PDF: PowerShell SSH Remoting Cheat Sheet.
FAQ
Can I use both WinRM and SSH remoting at the same time?
Yes. The two transports coexist. WinRM continues to work for any classic -ComputerName call; SSH activates when you use -HostName (note the parameter name difference).
Does SSH-based remoting work on Windows PowerShell 5.1?
No. SSH transport is a PowerShell 7 feature. The remote can be 5.1 only via WinRM; for SSH you need pwsh 7 on both ends.
Can I run interactive sudo commands on Linux through PowerShell remoting?
Yes if your sudo configuration allows it without TTY allocation, otherwise the command will hang waiting for a password. The cleaner pattern is to configure passwordless sudo for specific scoped commands the script runs.
Is the connection encrypted?
Yes โ it is plain SSH. The same encryption guarantees as any other SSH session apply.
Can I disconnect and reconnect to a long-running session?
Not currently โ disconnected sessions are a WinRM-only feature. Use tmux or Start-Job on the remote for long-running work that must survive client disconnect.
Does the remote need to be reachable directly?
No โ use SSH ProxyJump as shown above. Any number of bastion hops works transparently.
How do I move files?
Use scp / sftp over the same SSH connection, or pass file content as a parameter to Invoke-Command for small files. Copy-Item -ToSession also works against an SSH-based PSSession.