Share a Windows PC Through SSH
Budget machine learning server based on Windows 10 Home, or how you can control and share your PC remotely through the network, without the need for an expensive Windows Server License.

Let’s say your company just got a new desktop PC in the office. It has a proper Nvidia GPU, 3 times the core count on people’s laptops, and 4 times the RAM. It was bought to run some Windows specific programs, but also with the idea that others could share it. I assume no proper big company would ever have this problem that we had, because they can afford proper enterprise level hardware sharing solutions.

Still, there is a simple solution for this niche problem: use SSH to share the Windows PC. Actually OpenSSH has been added to Windows since autumn 2018. With the OpenSSH Server enabled, one could do anything that can be done from an elevated PowerShell (which is pretty much everything that mattered for us: run Python scripts, manage Docker containers, and access all the WSL distros).

In this post, I’ll copy form the official guide on how to set up the OpenSSH server so that you can SSH into your Windows machine. I’ll also list some example use cases and some debugging tips. As always, if you are here just to copy some quick commands, jump to the end section.

Setting Up

The client for OpenSSH should have been shipped with your Windows. If you go to C:\Windows\System32\OpenSSH you probably will see a few .exe files with familiar names like ssh, scp, and sftp. Just open PowerShell (or the Command Prompt), and type these usual SSH commands, they should work just like the ordinary SSH under Unix-like systems. You don’t need PuTTY to do SSH from Windows.

Install

In case your system doesn’t have SSH, manually installing it is also possible from an elevated PowerShell (you can launch it by right click Start button, then click Windows PowerShell (Admin)):

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

You might as well install the OpenSSH Server because that enables remote control:

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

Double check if you have installed both the server and the client with:

Get-WindowsCapability -Online | ? Name -like 'OpenSSH*'

You should get something like:

Name  : OpenSSH.Client~~~~0.0.1.0
State : Installed

Name  : OpenSSH.Server~~~~0.0.1.0
State : Installed

Start

Once you have the server installed, start it with:

Start-Service sshd

Check it with:

Get-Service sshd

If you get:

Status   Name               DisplayName
------   ----               -----------
Running  sshd               OpenSSH SSH Server

It means that the server is running. You can already SSH to localhost:

ssh localhost

It’ll use your current Windows user and ask for the password, which is your current Windows login password.

Config

Auto Restart

The service may not auto restart if you turn off your computer, unless you do:

Set-Service -Name sshd -StartupType 'Automatic'

Firewall

Windows should have opened the firewall automatically. Check it with

Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP"

In case it didn’t, manually open the firewall with:

New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22

Or you can search for the Windows Defender Firewall with Advanced Security tool to manage your firewall manually from a GUI. Just note that you should never brainlessly open ports on firewall, especially the special ports like 22 (SSH) and 3389 (Remote Desktop). You are always suggested to use strong passwords (or better, use key-based authentication), limit access to specific network (e. g., LAN only, or IP whitelist), and change the default ports to some random ports.

Default Shell

It is possible to configure the default shell (the shell you ends up in once logged in) for SSH. The default should be the classic Command Prompt, which is fine (because you can switch to the other shells from cmd by simply typing powershell or wsl). But here is how you can set it to PowerShell, which offers a more modern Windows shell experience:

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

Alternatively, if you are certain that all you would need to do can be done from the default WSL distro, you can set the default shell to wsl, which will give you a more Linux-like experience.

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

sshd_config

Additional settings are located at %programdata%\ssh\sshd_config, where you can find the familiar configuration options.

Credentials

Username and Password

By default the SSH server is set up to allow password login. All of the user accounts on the PC can now be used for remote login. If you use a Microsoft Account instead of a local account, you can also use the full email address as the username (e. g., ssh name@email.com@<ip address>), and your Microsoft Account password. Note that if you use Windows Hello Pin, it would not automatically work as the SSH password because it was supposed to be used only when you have physical access to the machine.

Public Key

Key-based authentication can be set up according to the documentation. While the typical Linux ssh-copy-id method is not available, the idea is the same: copy your public key to the .ssh directory, then set the file permissions. For standard users (if the user is not an administrator), even the path is the same: just add the public key to ~/.ssh/authorized_keys (e. g., C:\Users\<username>\.ssh\).

For administrators, however, it’s a different file: %programdata%\ssh\administrators_authorized_keys (e. g., C:\ProgramData\ssh). You also need to set its permissions. From an elevated PowerShell:

icacls.exe "$ENV:programdata\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"

The documentation used scp to copy the public key, which doesn’t work when I was trying to copy a key remotely from a Linux machine. Also the documented example will overwrite the authorized key file every time you run it. Alternatively you can just manually copy the key to your remote machine, create the authorized_keys or administrators_authorized_keys on a directory with no permission problems, copy it to ~/.ssh/ or $ENV:programdata\ssh, then run the icacls command.

IP Address

Of course this depends on how your PC connects to the Internet. We only use the computer within our local network (under a same Wi-Fi), so it was something like 10.**.**.**. You can find it with the ipconfig command in PowerShell, and find the IPv4 Address under Wireless LAN adapter Wi-Fi or something like Ethernet adapter (if it’s not on Wi-Fi).

Alternatively you can use something like ZeroTier to configure a fixed IP address, as well as getting a software-defined local network (so that you can config your firewall accordingly to prevent others in the same Wi-Fi from brute-force attacking your PC).

Use Cases

Obviously once you SSH into a Windows machine, you can use any of the command line tools as usual, such as the ultimate fix for any problem: Restart-Computer. Here is a non-exhaustive list of other things that we usually do through SSH on Windows:

Switch to the Elevated PowerShell

There is no counterpart for Linux’s sudo in Windows PowerShell. But SSH provides a weird but convenient work around. If you SSH into Windows with an administrator’s credentials, you enter the administrator PowerShell (or the administrator Command Prompt). This is handy if you are in a usual session of PowerShell and suddenly want to switch to the admin: just ssh localhost instead of sudo su!

Similarly, if you are currently in a WSL shell session, you can also use SSH to access the Windows PowerShell. In WSL1, just ssh <win-username>@localhost (you can omit the <win-username> part if your WSL and Windows share a same username). In WSL2, you’ll need to find out the IP of the Windows host relative to the WSL2 virtual machine, thus the full command is this monstrous thing: ssh <win-username>@$(cat /etc/resolv.conf | grep nameserver | awk '{print $2; exit;}') (I suggest make use of alias if you are going to do this a lot).

Remote Control of the WSL

If, as mentioned above, you set the default shell as wsl.exe, you’ll end up directly in your default WSL distro. Alternatively, you can use wsl commands to enter different WSL distros or even manage them. Either way you can manage WSL things from a remote SSH session.

Note that this only works with the in-Windows version of WSL and not the WSL from Microsoft Store, because Store WSL isn’t accessible from Session 0. With the Store version of WSL, when you SSH into PowerShell, then run wsl, you are only going to get this error:

The file cannot be accessed by the system.

If you want to stay with the Store version WSL, here is a workaround.

Manage Networking Applications

A computer connected to a network is just like a server (except it auto-restarts every once in a while to apply updates in the case of non-server versions of Windows). To use it as a budget machine learning server, just add --ip 0.0.0.0 (or you can specify the IP) to your usual Jupyter Lab commands, then use the IP address of the computer to access it from other computers.

We also installed Docker Desktop to deploy Docker based apps like databases, NLP API servers, and containerized Jupyter environments. Docker CLI works automatically in elevated PowerShell upon installation (and can be easily configured to work under WSL2), providing a seamless experience.

With the help of Docker Desktop and SSH, Windows can also serve as your home lab server. You can config it to run apps with Web UIs (qBittorrent, Jellyfin, Calibre-Web, etc.), then expose the ports to your home network for other devices to access. Just like what you would do with a Linux box.

Port Forwarding

Actually Windows has built-in port forwarding function with netsh. But if you need a cross-platform solution, you can now use the SSH tunnels.

Debug Stuff Remotely

Basically these are just PowerShell tricks. But since SSH gives you remote access to a PowerShell session, this is why you need to learn some PowerShell cmdlets.

(Re)Start Apps or the System

Nothing fixes a Windows App (or the computer itself) better than a restart.

off-and-on

To restart the computer from PowerShell:

Restart-Computer

But of course sometimes you only want to restart the app or service. For example, to restart Docker:

Start-Process 'C:\Program Files\Docker\Docker\Docker Desktop.exe'

It is typically used when I have tortured the system so hard that Docker Desktop simply died.

Network

Windows has this behavior of occupying large number of ports randomly. If you deploy a Docker containers and expose it to a port that happened to be taken by Windows, you may encounter a weird situation: the container is running, the port on Windows Firewall is opened, but you cannot access it remotely.

You can list the taken ports with PowerShell:

netsh interface ipv4 show excludedportrange protocol=tcp

And make sure <your port> never gets taken with:

netsh int ipv4 add excludedportrange protocol=tcp startport=<your port> numberofports=1

Debug SSH

By default, OpenSSH logs can be found from the Event Viewer, under Applications and Services Logs. Using it for occasional checking is very straightforward. It can also be configured otherwise if necessary.

Just List the Commands

Here are the commands for setting up OpenSSH server and client on Windows, copied from the official documentation.

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

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

# Start the sshd service
Start-Service sshd

# OPTIONAL but recommended:
Set-Service -Name sshd -StartupType 'Automatic'

# Confirm the Firewall rule is configured. It should be created automatically by setup. Run the following to verify
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
    Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
    New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
} else {
    Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
}

Last modified on 2023-01-08