Skip to content

PowerShell Remoting over SSH

How to enable cross-platform PowerShell remoting using SSH in PowerShell 7+, replacing WinRM with SSH key authentication for secure management of Windows, Linux, and macOS systems.

In PowerShell 7+, PowerShell Remoting supports the Secure Shell (SSH) protocol as an alternative transport layer to WinRM. SSH-based remoting enables true cross-platform management between Windows, Linux, and macOS endpoints over standard port 22, replacing complex WinRM and Kerberos workgroup configurations with industry-standard SSH key-based authentication.

SSH vs. WinRM Transport Architecture

While traditional PowerShell Remoting relies on Windows Remote Management (WinRM) over ports 5985/5986, SSH remoting tunnels the PowerShell remoting protocol (MS-PSRP) directly through a standard OpenSSH encrypted connection.

Feature WinRM Remoting SSH Remoting
PowerShell Version Windows PowerShell 5.1 & PS 7+ PowerShell 7+ (pwsh) only
Supported Operating Systems Primarily Windows (limited Linux via OMI) Windows, Linux, macOS
Network Port 5985 (HTTP), 5986 (HTTPS) 22 (SSH)
Target Parameter -ComputerName -HostName
Authentication Kerberos, NTLM, Negotiate, Basic SSH Key Pairs, Passwords
Firewall Setup Requires custom WS-Management ports Uses existing enterprise SSH infrastructure
Cross-OS remoting ❌ No ✅ Yes

Prerequisites

Both the local and remote machines need:

  • PowerShell 7+ installed.
  • An SSH server running (OpenSSH Server on Windows, or the built-in SSH daemon on Linux/macOS).
  • The remote machine's SSH server configured with a subsystem entry pointing to PowerShell.

Installing OpenSSH Server on Windows

Consolepowershell
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic

where:

  • Add-WindowsCapability installs the OpenSSH Server optional feature.
  • Start-Service sshd starts the SSH daemon immediately.
  • Set-Service -StartupType Automatic ensures SSH starts automatically on boot.

Configuring the SSH Subsystem for PowerShell

For SSH remoting to launch PowerShell (instead of a normal shell), the SSH server's configuration file must register PowerShell as a subsystem.

Target Configuration on Windows

On a Windows host, OpenSSH Server can be installed via Windows Optional Features. Once installed, edit C:\ProgramData\ssh\sshd_config to register the pwsh.exe binary as a subsystem.

sshd_configpowershell
Subsystem powershell C:/Program Files/PowerShell/7/pwsh.exe -sshs -NoLogo

where:

  • Subsystem powershell defines a named subsystem called powershell.
  • -sshs a required internal parameter flag instructing pwsh.exe to run in SSH Server Mode.
  • -NoLogo suppresses the startup banner upon connection initialization.
configure-windows-sshd.ps1powershell
$sshdConfig = "$env:ProgramData\ssh\sshd_config"
$subsystemLine = 'Subsystem powershell "C:/Program Files/PowerShell/7/pwsh.exe" -sshs -nologo'

if ((Get-Content $sshdConfig) -notcontains $subsystemLine) {
    Add-Content -Path $sshdConfig -Value "`n$subsystemLine"
    Restart-Service -Name "sshd" -Force
    Write-Output "PowerShell SSH Subsystem registered and sshd restarted."
}

where:

  • $env:ProgramData\ssh\sshd_config is the default configuration file path for OpenSSH on Windows.
  • Restart-Service -Name "sshd" restarts the OpenSSH service to apply configuration changes.
Consolepowershell
./configure-windows-sshd.ps1
Outputplaintext
PowerShell SSH Subsystem registered and sshd restarted.

Target Configuration on Linux

On a Linux distribution (such as Ubuntu or RHEL) with PowerShell 7 installed, edit /etc/ssh/sshd_config:

/etc/ssh/sshd_configbash
Subsystem powershell /usr/bin/pwsh -sshs -nologo

After modifying the file, restart the SSH daemon:

Consolepowershell
sudo systemctl restart sshd

Connecting with New-PSSession over SSH

Once configured, New-PSSession and Invoke-Command accept SSH-specific parameters instead of -ComputerName alone.

Consolepowershell
$session = New-PSSession -HostName "linuxserver01" -UserName "admin" -SSHTransport

where:

  • -HostName specifies the target machine's hostname or IP address.
  • -UserName specifies the SSH username to authenticate with.
  • -SSHTransport tells PowerShell to use SSH instead of WinRM as the connection transport.
Outputplaintext
Password:

Connecting with an SSH Key

Consolepowershell
$session = New-PSSession -HostName "linuxserver01" -UserName "admin" -KeyFilePath "$HOME/.ssh/id_rsa"

where -KeyFilePath is the path to a private SSH key used for passwordless authentication.

Interactive Session Example

interactive-ssh-session.ps1powershell
Enter-PSSession -HostName "192.168.1.50" `
    -UserName "ubuntu" `
    -KeyFilePath "~/.ssh/id_ed25519"

where:

  • Enter-PSSession starts an interactive remote session.
  • -HostName directs the connection over SSH to the target IP address or hostname.
  • -UserName the remote user account to authenticate as.
  • -KeyFilePath path to the local OpenSSH private authentication key file (bypasses interactive password prompts).
Consolepowershell
./interactive-ssh-session.ps1
Outputplaintext
[ubuntu@192.168.1.50]: PS /home/ubuntu> $PSVersionTable.OS
Linux 5.15.0-101-generic #111-Ubuntu SMP
[ubuntu@192.168.1.50]: PS /home/ubuntu> Exit-PSSession
PS C:\Users\Admin>

Interactive Sessions with Enter-PSSession over SSH

Consolepowershell
Enter-PSSession -HostName "linuxserver01" -UserName "admin"
Outputplaintext
[linuxserver01]: PS /home/admin>

Running Commands over SSH with Invoke-Command

The Invoke-Command cmdlet executes script blocks across one or more SSH endpoints concurrently. Even when executing on a remote Linux server, PowerShell serializes command output into .NET objects and transmits them back to the caller.

Consolepowershell
Invoke-Command -HostName "linuxserver01" -UserName "admin" -ScriptBlock { Get-Process }
Outputplaintext
ProcessName   Id   PSComputerName
-----------   --   --------------
bash          1204 linuxserver01
pwsh          1215 linuxserver01

One-to-Many Commands over SSH

invoke-ssh-command.ps1powershell
$linuxTargets = @("srv-linux01.corp.internal", "srv-linux02.corp.internal")

Invoke-Command -HostName $linuxTargets -UserName "admin_ops" -KeyFilePath "~/.ssh/id_rsa" -ScriptBlock {
    [PSCustomObject]@{
        HostName    = $env:HOSTNAME
        Kernel      = (uname -r)
        UptimeDays  = [math]::Round(((Get-Content /proc/uptime).Split(' ')[0] / 86400), 1)
        ProcessCount= (Get-Process).Count
    }
} | Select-Object -Property PSComputerName, HostName, Kernel, UptimeDays, ProcessCount

where:

  • Invoke-Command runs code across remote hosts in parallel.
  • -HostName list of remote nodes to connect to via SSH.
  • uname -r executes native Linux CLI utilities directly inside the remote script block.
  • Get-Content /proc/uptime reads Linux virtual filesystem counters as standard PowerShell objects.
  • PSComputerName is an automatically attached property containing the destination hostname.
Consolepowershell
./invoke-ssh-command.ps1
Outputplaintext
PSComputerName              HostName     Kernel            UptimeDays ProcessCount
--------------              --------     ------            ---------- ------------
srv-linux01.corp.internal   srv-linux01  5.15.0-91-generic       12.4          148
srv-linux02.corp.internal   srv-linux02  5.15.0-91-generic       44.1          162

Connecting Windows ↔ Linux in Both Directions

SSH remoting functions symmetrically: a Windows machine can connect to Linux, and a Linux machine running PowerShell 7 can connect to Windows. This works as long as both systems have PowerShell and SSH correctly configured.

Consolepowershell
Enter-PSSession -HostName "192.168.1.50" -UserName "ubuntu"
Consolepowershell
Enter-PSSession -HostName "192.168.1.10" -UserName "Administrator"

Practical Usage

Cross-Platform Server Inventory Script

A common use case for SSH remoting is gathering system information from a mixed fleet of Windows and Linux servers in one script.

cross-platform-inventory.ps1powershell
$targets = @(
    @{ HostName = "winserver01"; UserName = "Administrator" },
    @{ HostName = "linuxserver01"; UserName = "admin" }
)

foreach ($target in $targets) {
    $session = New-PSSession -HostName $target.HostName -UserName $target.UserName -KeyFilePath "$HOME/.ssh/id_rsa"

    Invoke-Command -Session $session -ScriptBlock {
        [PSCustomObject]@{
            ComputerName = $env:COMPUTERNAME ?? (hostname)
            OS           = $PSVersionTable.OS
            PSVersion    = $PSVersionTable.PSVersion.ToString()
        }
    }

    Remove-PSSession -Session $session
}

where:

  • $targets is an array of hashtables defining each remote host and its SSH username.
  • $env:COMPUTERNAME ?? (hostname) uses the null-coalescing operator to fall back to the Linux hostname command if the Windows-only environment variable is empty.
  • $PSVersionTable.OS reports the operating system of each remote target.
Consolepowershell
./cross-platform-inventory.ps1
Outputplaintext
ComputerName   OS                              PSVersion
------------   --                              ---------
WINSERVER01    Microsoft Windows 10.0.20348    7.4.2
linuxserver01  Linux 5.15.0-91-generic          7.4.2

Managing Hybrid Infrastructure (Windows & Linux)

In modern enterprise networks, administrators often manage both Windows and Linux nodes within a single script. SSH Remoting standardizes connection parameters across operating systems without requiring WinRM listeners on Linux.

manage-hybrid-fleet.ps1powershell
$sessionWin   = New-PSSession -HostName "win-app01.corp"   -UserName "winadmin"  -KeyFilePath "~/.ssh/id_ed25519"
$sessionLinux = New-PSSession -HostName "linux-db01.corp" -UserName "dbadmin"   -KeyFilePath "~/.ssh/id_ed25519"

$hybridSessions = @($sessionWin, $sessionLinux)

$fleetReport = Invoke-Command -Session $hybridSessions -ScriptBlock {
    [PSCustomObject]@{
        OperatingSystem = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
        Architecture    = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
        PSVersion       = $PSVersionTable.PSVersion.ToString()
        MemoryAvailable = if ($IsWindows) {
            [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)
        } else {
            [math]::Round(([int](Get-Content /proc/meminfo | Select-String "MemAvailable" | ForEach-Object { $_.Line.Split()[1] }) / 1MB), 2)
        }
    }
}

$hybridSessions | Remove-PSSession

$fleetReport | Select-Object -Property PSComputerName, OperatingSystem, Architecture, PSVersion, MemoryAvailable | 
    Format-Table -AutoSize

where:

  • New-PSSession -HostName instantiates persistent SSH-backed runspaces.
  • [System.Runtime.InteropServices.RuntimeInformation] cross-platform .NET class that returns operating system and architecture information reliably on Windows, macOS, and Linux.
  • $IsWindows is a PowerShell automatic boolean variable that returns $true on Windows and $false on Linux/macOS, used to execute platform-specific inspection logic.
  • Remove-PSSession closes the SSH connections and frees remote resources.
Consolepowershell
./manage-hybrid-fleet.ps1
Outputplaintext
PSComputerName   OperatingSystem                       Architecture PSVersion MemoryAvailable
--------------   ---------------                       ------------ --------- ---------------
win-app01.corp   Microsoft Windows 10.0.20348          X64          7.4.1                6.42
linux-db01.corp  Linux 5.15.0-101-generic #111-Ubuntu  X64          7.4.1               11.85
Navigation