Windows SSH Remote Login

Enabling SSH remote access on Windows typically requires Windows’ built-in OpenSSH feature. Below are step-by-step instructions:

Check and Install OpenSSH

  1. Check whether OpenSSH is already installed:

    • Open Settings > Apps > Apps & features > Manage optional features.
    • Look for OpenSSH Server in the list. If found, it is already installed.
  2. Install OpenSSH:

    • If OpenSSH Server is not listed, click Add a feature, locate OpenSSH Server in the list, click it, then click Install.

Start and Configure the OpenSSH Service

  1. Start the OpenSSH service:

    • After installation, open Command Prompt (run as administrator).
    • Type net start sshd to start the OpenSSH service. To make it start automatically at boot, run sc config sshd start= auto.
  2. Configure the firewall:

    • Ensure the Windows firewall allows SSH connections. Go to Control Panel > System and Security > Windows Defender Firewall > Advanced settings, create an inbound rule to allow connections on TCP port 22.

Get the IP Address and Test the Connection

  1. Get the IP address:

    • To connect from another machine, you’ll need the IP address of the Windows PC where SSH was enabled. Run ipconfig at the command prompt to find it.
  2. Connection test:

    • Use an SSH client (e.g., PuTTY, Termius) from another computer or device to connect, using the format ssh username@your_ip_address, where username is the Windows account name and your_ip_address is the address you just obtained.

Modify Configuration

Avoid logging in with passwords—this is a must-avoid trap. Always use public keys to log in.
We need to disable password login and enable public-key login by adjusting the configuration.

Because the file is protected, editing it requires special privileges, and its folder and file permissions must be set to specific values. Using a script is strongly recommended.

# Check for admin rights
$elevated = [bool]([System.Security.Principal.WindowsPrincipal]::new(
    [System.Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))

if (-not $elevated) {
    Write-Error "Please run this script with administrator rights"
    exit 1
}

# 1. Check and install the OpenSSH server if necessary
Write-Host "Checking OpenSSH server installation status..."
$capability = Get-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

if ($capability.State -ne 'Installed') {
    Write-Host "Installing OpenSSH server..."
    Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null
}

# 2. Start and set the OpenSSH service to auto-start
Write-Host "Configuring SSH service..."
$service = Get-Service sshd -ErrorAction SilentlyContinue
if (-not $service) {
    Write-Error "OpenSSH service failed to install"
    exit 1
}

if ($service.Status -ne 'Running') {
    Start-Service sshd
}
Set-Service sshd -StartupType Automatic

# 3. Edit the configuration file
$configPath = "C:\ProgramData\ssh\sshd_config"
if (Test-Path $configPath) {
    Write-Host "Backing up original configuration file..."
    Copy-Item $configPath "$configPath.bak" -Force
} else {
    Write-Error "Configuration file not found: $configPath"
    exit 1
}

Write-Host "Modifying SSH configuration..."
$config = Get-Content -Path $configPath -Raw

# Enable pubkey authentication and disable password login
$config = $config -replace '^#?PubkeyAuthentication .*$','PubkeyAuthentication yes' `
                  -replace '^#?PasswordAuthentication .*$','PasswordAuthentication no'

# Ensure necessary configs are present
if ($config -notmatch 'PubkeyAuthentication') {
    $config += "`nPubkeyAuthentication yes"
}
if ($config -notmatch 'PasswordAuthentication') {
    $config += "`nPasswordAuthentication no"
}

# Write the new configuration
$config | Set-Content -Path $configPath -Encoding UTF8

Confirm authorized_keys Permissions

# normal user
$authKeys = "$env:USERPROFILE\.ssh\authorized_keys"
icacls $authKeys /inheritance:r /grant "$($env:USERNAME):F" /grant "SYSTEM:F"
icacls "$env:USERPROFILE\.ssh" /inheritance:r /grant "$($env:USERNAME):F" /grant "SYSTEM:F"

# administrator
$adminAuth = "C:\ProgramData\ssh\administrators_authorized_keys"
icacls $adminAuth /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"

Set Firewall Rules

# Allow SSH port
New-NetFirewallRule -DisplayName "OpenSSH Server (sshd)" -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22

Add Public Keys

Normal User

# normal user
$userProfile = $env:USERPROFILE
$sshDir = Join-Path $userProfile ".ssh"
$authorizedKeysPath = Join-Path $sshDir "authorized_keys"
$PublicKeyPath = "D:\public_keys\id_rsa.pub"

# Create .ssh directory
if (-not (Test-Path $sshDir)) {
    New-Item -ItemType Directory -Path $sshDir | Out-Null
}

# Set .ssh directory permissions
$currentUser = "$env:USERDOMAIN\$env:USERNAME"
$acl = Get-Acl $sshDir
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    $currentUser, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$acl.AddAccessRule($rule)
Set-Acl $sshDir $acl

# Add public key
if (Test-Path $PublicKeyPath) {
    $pubKey = Get-Content -Path $PublicKeyPath -Raw
    if ($pubKey) {
        # Ensure newline at end
        if (-not $pubKey.EndsWith("`n")) {
            $pubKey += "`n"
        }

        # Append key
        Add-Content -Path $authorizedKeysPath -Value $pubKey -Encoding UTF8

        # Set file permissions
        $acl = Get-Acl $authorizedKeysPath
        $acl.SetSecurityDescriptorRule(
            (New-Object System.Security.AccessControl.FileSystemAccessRule(
                $currentUser, "FullControl", "None", "None", "Allow"
            ))
        )
        Set-Acl $authorizedKeysPath $acl
    }
} else {
    Write-Error "Public key file not found: $PublicKeyPath"
    exit 1
}

# Restart SSH service
Write-Host "Restarting SSH service..."
Restart-Service sshd

Administrator User

# administrator
$adminSshDir = "C:\ProgramData\ssh"
$adminAuthKeysPath = Join-Path $adminSshDir "administrators_authorized_keys"
$adminPublicKeyPath = "D:\public_keys\id_rsa.pub"

# Create admin SSH directory
if (-not (Test-Path $adminSshDir)) {
    New-Item -ItemType Directory -Path $adminSshDir | Out-Null
}

# Set admin SSH directory permissions
$adminAcl = Get-Acl $adminSshDir
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    "Administrators", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$adminAcl.AddAccessRule($adminRule)
Set-Acl $adminSshDir $adminAcl

# Add admin public key
if (Test-Path $adminPublicKeyPath) {
    $adminPubKey = Get-Content -Path $adminPublicKeyPath -Raw
    if ($adminPubKey) {
        # Ensure newline at end
        if (-not $adminPubKey.EndsWith("`n")) {
            $adminPubKey += "`n"
        }

        # Append key
        Add-Content -Path $adminAuthKeysPath -Value $adminPubKey -Encoding UTF8

        # Set file permissions
        $adminAcl = Get-Acl $adminAuthKeysPath
        $adminAcl.SetSecurityDescriptorRule(
            (New-Object System.Security.AccessControl.FileSystemAccessRule(
                "Administrators", "FullControl", "None", "None", "Allow"
            ))
        )
        Set-Acl $adminAuthKeysPath $adminAcl
    }
} else {
    Write-Error "Admin public key file not found: $adminPublicKeyPath"
    exit 1
}

# Restart SSH service
Write-Host "Restarting SSH service..."
Restart-Service sshd