Control an Ubuntu Laptop from Windows 11: SSH, Hermes and GNOME Remote Desktop
This tutorial turns a Windows 11 laptop into the control seat for a physical Ubuntu 26.04 laptop on the same home network. You get passwordless SSH, Hermes Agent running its terminal commands on Ubuntu, and Ubuntu’s built-in GNOME Remote Desktop in two modes: the real screen, and a separate login session, each one click away.
I use this setup on my own machines, and I repeat it every time I add a laptop. The steps below are the ones I follow, with my own addresses, names and keys replaced by example values. It is the companion of Control a Kali Linux Laptop from Windows 11: same Windows laptop, and the Kali laptop sits on the same network.
What this tutorial covers:
- the LAN and a static IP;
- SSH key access;
- Hermes Mode A (agent on Windows, commands on Ubuntu);
- Ubuntu’s built-in GNOME Remote Desktop, in two modes: Desktop Sharing (the real screen) and Remote Login (a separate session);
- one-click desktop shortcuts with an Ubuntu icon.
0. Before you start
Section titled “0. Before you start”0.1 The values this tutorial uses
Section titled “0.1 The values this tutorial uses”Every command in this tutorial uses the example values below, so you can copy it as it is. These are not your values. Replace each one with your own before you run a command.
| What | Value in this tutorial | How to find yours |
|---|---|---|
| Home network | 192.168.50.0/24 |
ipconfig on Windows |
| Router (gateway and DNS) | 192.168.50.1 |
ipconfig → Default Gateway |
| Windows laptop address | 192.168.50.10 |
ipconfig → Wi-Fi → IPv4 Address |
| Ubuntu laptop address (static) | 192.168.50.21 |
You choose it in Phase 1 |
| Kali laptop address (other tutorial) | 192.168.50.20 |
Only if you also follow the Kali tutorial |
| Wi-Fi connection name on Ubuntu | HomeWiFi |
nmcli connection show --active on Ubuntu |
| Ubuntu Wi-Fi interface | wlp2s0 |
ip -4 -br addr on Ubuntu |
| Ubuntu account | labuser |
whoami on Ubuntu |
| Ubuntu hostname | lab-Ubuntu |
hostname on Ubuntu |
| Windows account folder | C:\Users\Lab User |
echo $env:USERPROFILE in PowerShell |
| SSH alias, key file and Hermes profile | ubuntu-lab, ubuntu_lab, ubuntu-lab |
You choose them in Phases 4 and 5 |
| Folder for the RDP files and the icon | C:\RemoteAccess |
You choose it in Appendix B |
The example Windows account folder contains a space on purpose. Many real account folders have one, and a space breaks a path that is not in quotes.
A short Your values note marks each command that holds a value from this table.
0.2 Name consistency
Section titled “0.2 Name consistency”- The SSH alias is
ubuntu-lab(hyphen). - The key file is
ubuntu_lab(underscore). - The machine’s hostname is
lab-Ubuntu. - The login user is
labuser. - The Hermes profile is
ubuntu-lab.
All the hostname spellings work with SSH because they share one Host block (Phase 4.3).
0.3 Ports and services
Section titled “0.3 Ports and services”| Port | Service on Ubuntu | Purpose | Windows uses |
|---|---|---|---|
| TCP 22 | OpenSSH (ssh.socket → ssh.service) |
SSH, Hermes Mode A | ssh ubuntu-lab, Hermes profile ubuntu-lab |
| TCP 3390 | GNOME Remote Desktop, user daemon: Desktop Sharing | The real screen | Shortcut Ubuntu Screen (C:\RemoteAccess\Ubuntu-Screen.rdp) |
| TCP 3389 | GNOME Remote Desktop, system daemon: Remote Login | A separate login session | Shortcut Ubuntu Login (C:\RemoteAccess\Ubuntu-Login.rdp) |
Both modes default to 3389. When both are enabled, Desktop Sharing moves to 3390 and Remote Login keeps 3389 (Ubuntu documentation).
0.4 What you need, and the versions this was tested with
Section titled “0.4 What you need, and the versions this was tested with”- A Windows 11 laptop with Hermes Desktop installed and signed in to a model provider, and PowerShell 7.3 or later.
- A physical Ubuntu 26.04 laptop with the default GNOME desktop, and an administrator account (a member of the
sudogroup). - Both laptops on the same home Wi-Fi network, which you control.
| Machine | Software | Tested version |
|---|---|---|
| Ubuntu | Ubuntu Desktop | 26.04.1 LTS, GNOME 50 on Wayland |
| Ubuntu | openssh-server | 1:10.2p1-2ubuntu3.8 |
| Ubuntu | GNOME | gnome-shell 50.1, gdm3 50.1, gnome-control-center 50.3 |
| Ubuntu | gnome-remote-desktop | 50.2 (grdctl command-line tool) |
| Ubuntu | Firewall | ufw installed, not enabled (ENABLED=no) |
| Ubuntu | tmux, Hermes | not installed (not needed for Mode A; tmux optional) |
| Windows | OpenSSH client | OpenSSH_for_Windows_9.5p2 |
| Windows | Hermes Desktop | Store package NousResearchInc.HermesAgent, agent v0.22.5 |
| Windows | Remote Desktop client | built-in mstsc.exe |
The example screens are a 4K Ubuntu panel at 200 % scale (1920×1080 logical) and a 4K Windows laptop at 200 %. The RDP settings in Phase 6.5 refer to these.
Administrator-account warning: labuser is an administrator, so the agent in profile ubuntu-lab can do anything labuser can.
- Never configure passwordless unrestricted
sudo. - Never put the sudo password or the RDP passwords in prompts,
.env, or scripts. - Keep Hermes command approvals on.
1. Understand the connections
Section titled “1. Understand the connections”1.1 Architecture
Section titled “1.1 Architecture”flowchart LR
subgraph W["Windows 11 (192.168.50.10)"]
HD["Hermes Desktop<br/>gateway: This device"]
PU["Profile ubuntu-lab<br/>(agent on Windows)"]
RS["Shortcut: Ubuntu Screen<br/>mstsc → :3390"]
RL["Shortcut: Ubuntu Login<br/>mstsc → :3389"]
end
subgraph U["Ubuntu laptop (192.168.50.21, user labuser)"]
SSHD["OpenSSH :22"]
DS["Desktop Sharing :3390<br/>(user daemon, real screen)"]
RLD["Remote Login :3389<br/>(system daemon → GDM → session)"]
end
HD --> PU
PU -- "Mode A: commands over SSH (key ubuntu_lab)" --> SSHD
RS -- "RDP login labuser + RDP password" --> DS
RL -- "RDP login labuser + RDP password, then Linux password at GDM" --> RLD
1.2 Hermes: Mode A only
Section titled “1.2 Hermes: Mode A only”The Hermes agent and model run on Windows. The profile ubuntu-lab sends only its terminal commands to Ubuntu over SSH. Nothing Hermes-related is installed on Ubuntu, and there is no Desktop SSH gateway for Ubuntu.
To add Mode B later, follow Phase 6 Option B of the Kali tutorial. Ubuntu’s shell is bash, so the ~/.zshenv step there does not apply.
1.3 Choose how to see Ubuntu
Section titled “1.3 Choose how to see Ubuntu”| Aspect | Desktop Sharing (“Ubuntu Screen”) | Remote Login (“Ubuntu Login”) |
|---|---|---|
| Shows | The real screen of the logged-in user (mirror-primary) |
A new session for your account, started remotely |
| Requires | You are logged in on the laptop | You are not logged in locally. Otherwise Ubuntu offers to end the local session |
| Resolution | Ubuntu’s own (for example 3840×2160), scaled by Windows to fit (smart sizing) |
Follows the Windows window (dynamic resolution); sharp |
| Daemon | systemctl --user service gnome-remote-desktop (runs as your account) |
system service gnome-remote-desktop + gnome-remote-desktop-handover |
| Where its login is stored | your GNOME login keyring | root-only GKeyFile under /var/lib/gnome-remote-desktop (on a laptop without a TPM chip) |
| Port (both enabled) | 3390 | 3389 |
Phase 1: Confirm the LAN and give Ubuntu its own static IP
Section titled “Phase 1: Confirm the LAN and give Ubuntu its own static IP”1.1 Confirm Windows’ address
Section titled “1.1 Confirm Windows’ address”ipconfig # Wireless LAN adapter Wi-Fi: 192.168.50.10 / 255.255.255.0 / 192.168.50.1Get-NetConnectionProfile # Wi-Fi -> Private1.2 Inspect Ubuntu’s network
Section titled “1.2 Inspect Ubuntu’s network”On the Ubuntu laptop, in a terminal (no sudo):
echo "host=$(hostname) user=$(whoami) shell=$SHELL session=$XDG_SESSION_TYPE desktop=$XDG_CURRENT_DESKTOP"grep PRETTY_NAME /etc/os-releaseip -4 -br addr | grep -v '^lo'ip route | grep defaultnmcli -t -f NAME,DEVICE connection show --activeExample output, before the static address is set:
host=lab-Ubuntu user=labuser shell=/bin/bash session=wayland desktop=ubuntu:GNOMEPRETTY_NAME="Ubuntu 26.04.1 LTS"wlp2s0 UP 192.168.50.20/24default via 192.168.50.1 dev wlp2s0 proto dhcp src 192.168.50.20 metric 600HomeWiFi:wlp2s0An address conflict to watch for. In the example above, the router’s DHCP gave Ubuntu 192.168.50.20, which is the Kali laptop’s static address from the other tutorial. This happens when Kali is switched off: the router considers the address free, because Kali’s static address is set on Kali only and is not reserved in the router.
Running both laptops like that makes SSH and RDP reach whichever one answers first. It also confuses Windows: lab-ubuntu.local resolves to the Kali address, and port 22 is refused.
Rule: every laptop gets its own static address. In this tutorial, Ubuntu gets 192.168.50.21. Before you choose an address, confirm from Windows that nothing answers there:
foreach ($ip in '192.168.50.21','192.168.50.22','192.168.50.23') { "{0}: ping={1}" -f $ip, (Test-Connection $ip -Count 1 -TimeoutSeconds 1 -Quiet) }# 192.168.50.21: ping=False (and no ARP entry), so it looks unused. Not absolute proof.If the router’s DHCP pool includes the address you choose, a future device could still receive it. If that ever happens, pick an address outside the pool.
1.3 Set the static address on Ubuntu (NetworkManager)
Section titled “1.3 Set the static address on Ubuntu (NetworkManager)”At the Ubuntu laptop’s own keyboard (the network drops briefly):
CON=$(nmcli -t -f NAME,DEVICE connection show --active | grep -v ':lo$' | head -1 | cut -d: -f1); echo "connection: $CON" # HomeWiFisudo nmcli connection modify "$CON" ipv4.method manual ipv4.addresses 192.168.50.21/24 ipv4.gateway 192.168.50.1 ipv4.dns 192.168.50.1sudo nmcli connection up "$CON"ip -4 -br addr | grep -v '^lo' # wlp2s0 UP 192.168.50.21/24GUI alternative: Settings → Wi-Fi → (gear on your network) → IPv4 → Manual, with address 192.168.50.21, netmask 255.255.255.0, gateway 192.168.50.1, DNS 192.168.50.1.
1.4 Verify
Section titled “1.4 Verify”nmcli -g ipv4.method,ipv4.addresses,ipv4.gateway,ipv4.dns,connection.autoconnect connection show HomeWiFi# manual / 192.168.50.21/24 / 192.168.50.1 / 192.168.50.1 / yesip route | head -2 # default via 192.168.50.1 dev wlp2s0 proto static metric 600ping -c 3 192.168.50.1 && getent hosts ubuntu.comReboot once and confirm the address persists. From Windows: ping 192.168.50.21.
Phase 2: Verify the existing Ubuntu account
Section titled “Phase 2: Verify the existing Ubuntu account”Do not create another user.
whoami; id; sudo -vExpected:
labuser, uid 1000.- Groups:
labuser adm cdrom sudo dip plugdev users lpadmin lxd.admletslabuserread system logs, including Remote Login’s, without sudo. - Shell
/bin/bash, so non-interactive SSH sees~/.local/binnormally (unlike Kali’s zsh). sudoasks for the password (sudo -n true→ “interactive authentication is required”). That is why you run everysudostep in this tutorial yourself, never through an automated tool.
Phase 3: Install and start the SSH server on Ubuntu
Section titled “Phase 3: Install and start the SSH server on Ubuntu”Ubuntu Desktop ships without an SSH server, so Windows sees port 22 refused at first.
sudo apt update && sudo apt install -y openssh-server && sudo systemctl enable --now sshssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub# 256 SHA256:<your Ubuntu host key fingerprint> root@lab-Ubuntu (ED25519) <- write this downThe install pulls in openssh-sftp-server, ssh-import-id, and ncurses-term, creates the sshd user, and enables socket activation (ssh.socket), so systemd starts sshd on the first connection. Verify:
systemctl is-active ssh.socket ssh.service # active / activesystemctl is-enabled ssh # enabledss -ltn | grep ':22 ' # LISTEN 0.0.0.0:22 and [::]:22grep -vE '^\s*(#|$)' /etc/ssh/sshd_config # stock: Include sshd_config.d, KbdInteractiveAuthentication no, UsePAM yes, X11Forwarding yes, ...The stock config allows both key and password login. You need the password exactly once, to install the key (Phase 4.4).
Firewall: ufw is installed but not enabled (/etc/ufw/ufw.conf: ENABLED=no), so ports 22, 3389, and 3390 need no rules. If you enable UFW later, allow the Windows laptop first:
sudo ufw allow from 192.168.50.10 to any port 22 proto tcpsudo ufw allow from 192.168.50.10 to any port 3389:3390 proto tcpOpenSSH 10 penalty: like Kali, Ubuntu’s sshd temporarily blocks a source address after repeated failed logins (PerSourcePenalties). Never leave a misconfigured client retrying (Kali tutorial, Phase 3.1).
Phase 4: Windows key, host key, and the ubuntu-lab alias
Section titled “Phase 4: Windows key, host key, and the ubuntu-lab alias”4.1 Create a dedicated key (PowerShell 7, normal user)
Section titled “4.1 Create a dedicated key (PowerShell 7, normal user)”$key = "$env:USERPROFILE\.ssh\ubuntu_lab"if (Test-Path $key) { 'ubuntu_lab already exists - leaving it untouched' } else { ssh-keygen -q -t ed25519 -N '' -C 'lab user@winpc ubuntu_lab' -f $key }ssh-keygen -lf "$key.pub" # 256 SHA256:... lab user@winpc ubuntu_lab (ED25519)if (ssh-keygen -y -P '' -f $key 2>$null) { 'no passphrase (required for Hermes)' }A separate key per machine (kali_lab, ubuntu_lab) means you can revoke one without touching the other. Never overwrite an existing key (answer n to Overwrite (y/n)?).
4.2 Record Ubuntu’s host key, verified out-of-band
Section titled “4.2 Record Ubuntu’s host key, verified out-of-band”Accept the host key only after comparing it with the fingerprint printed on the Ubuntu screen in Phase 3. The Windows ssh-keyscan cannot fetch it:
choose_kex: unsupported KEX method sntrup761x25519-sha512@openssh.comThis is a limitation of OpenSSH for Windows 9.5 (LibreSSL build). Capture the key with ssh itself, attempting no login, into a temporary file. Then compare it and append it only if it matches:
$expected = 'SHA256:PASTE-THE-FINGERPRINT-FROM-PHASE-3'$tmp = Join-Path $env:TEMP 'ubuntu_known_hosts'ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o "UserKnownHostsFile=$tmp" -o HostKeyAlgorithms=ssh-ed25519 ` -o PubkeyAuthentication=no -o PasswordAuthentication=no -o KbdInteractiveAuthentication=no labuser@192.168.50.21 exit 2>&1 | Out-Null$line = Get-Content $tmp | Where-Object { $_ -match '^192\.168\.50\.21 ssh-ed25519 ' } | Select-Object -First 1$fp = ((ssh-keygen -lf $tmp) -split ' ')[1]; [IO.File]::Delete($tmp)if ($fp -eq $expected) { Add-Content "$env:USERPROFILE\.ssh\known_hosts" $line; 'MATCH - added' } else { 'MISMATCH - stop' }The simpler alternative is to answer yes at the first interactive ssh labuser@192.168.50.21, but only after comparing the fingerprint it shows.
4.3 Add the alias block (%USERPROFILE%\.ssh\config)
Section titled “4.3 Add the alias block (%USERPROFILE%\.ssh\config)”Append this, keeping any Kali and other blocks:
# Ubuntu laptop (lab-Ubuntu) on the home LAN, static 192.168.50.21Host ubuntu-lab lab-ubuntu lab-Ubuntu 192.168.50.21 HostName 192.168.50.21 User labuser Port 22 IdentityFile ~/.ssh/ubuntu_lab IdentitiesOnly yesWhy every name is listed:
ubuntu-labis the short alias.lab-Ubuntu/lab-ubuntuis what you naturally type (the hostname). Without these entries, Windows resolves that name over mDNS (Ubuntu’s local name broadcasting) to an IPv6 link-local address (fe80::…%11) and asks you to trust a “new” host.- The raw IP covers tools that connect by address.
Because all names share one HostName, one known_hosts entry serves them all. Alias matching is case-sensitive, so both spellings of the hostname are listed.
foreach ($n in 'ubuntu-lab','lab-ubuntu','lab-Ubuntu','192.168.50.21') { "$n -> " + ((ssh -G $n | Select-String '^(user|hostname|identityfile) ' | ForEach-Object Line) -join ' | ') }# each: user labuser | hostname 192.168.50.21 | identityfile ~/.ssh/ubuntu_lab4.4 Install the public key on Ubuntu (asks the Linux password once)
Section titled “4.4 Install the public key on Ubuntu (asks the Linux password once)”Run this in your own PowerShell window:
$pub = Get-Content "$env:USERPROFILE\.ssh\ubuntu_lab.pub"ssh ubuntu-lab "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '$pub' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && echo KEY-INSTALLED"Type your Ubuntu account’s Linux password, then expect KEY-INSTALLED.
Do not pipe the key into ssh (Get-Content key.pub | ssh ...). On the first connection, the “Are you sure you want to continue connecting (yes/no)?” question reads from that same input stream. It gets the key text instead of your yes and aborts with Host key verification failed. Passing the key inside the remote command keeps the input free.
Running the install twice appends the key twice. Remove duplicates while keeping order:
awk '!seen[$0]++' ~/.ssh/authorized_keys > ~/.ssh/ak.tmp && mv ~/.ssh/ak.tmp ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keyswc -l < ~/.ssh/authorized_keys # 14.5 Test
Section titled “4.5 Test”ssh -o BatchMode=yes ubuntu-lab "hostname && whoami && pwd" # lab-Ubuntu / labuser / /home/labuser (no prompts)ssh -o BatchMode=yes lab-Ubuntu "hostname" # lab-Ubuntussh ubuntu-lab # interactive: "Welcome to Ubuntu 26.04.1 LTS ..."BatchMode=yes must pass, because Hermes connects the same way.
Phase 5: Hermes Mode A with profile ubuntu-lab
Section titled “Phase 5: Hermes Mode A with profile ubuntu-lab”5.1 Create the profile
Section titled “5.1 Create the profile”If you followed the Kali tutorial, clone its working profile:
hermes profile create ubuntu-lab --clone-from kali-lab --no-alias ` --description "Agent on Windows; terminal commands run on the Ubuntu laptop over SSH"Expected output:
Cloned config, .env, SOUL.md, and skills from kali-lab, plus the installed plugins.⚠ This profile has no API keys yet: harmless when your model provider uses a sign-in (such as Nous Portal) instead of an API key. The model is the same as inkali-lab.
Without a Kali profile, clone your default profile instead, then switch its terminal backend to SSH:
hermes profile create ubuntu-lab --clone-from default --no-alias ` --description "Agent on Windows; terminal commands run on the Ubuntu laptop over SSH"hermes -p ubuntu-lab config set terminal.backend ssh--no-alias skips the wrapper command; use hermes -p ubuntu-lab .... Confirm the backend:
hermes -p ubuntu-lab config get terminal.backend # ssh5.2 Point its SSH settings at Ubuntu
Section titled “5.2 Point its SSH settings at Ubuntu”A profile cloned from kali-lab still targets Kali. This script replaces any TERMINAL_SSH_* lines and keeps everything else (PowerShell 7):
$envFile = "$env:LOCALAPPDATA\hermes\profiles\ubuntu-lab\.env"if (-not (Test-Path $envFile)) { New-Item -ItemType File -Force $envFile | Out-Null }$lines = @(Get-Content $envFile | Where-Object { $_ -notmatch '^\s*TERMINAL_SSH_' -and $_ -notmatch '^# SSH terminal backend:' })$lines += @("# SSH terminal backend: this profile's commands run on the Ubuntu laptop as 'labuser'", 'TERMINAL_SSH_HOST=192.168.50.21', 'TERMINAL_SSH_USER=labuser', 'TERMINAL_SSH_PORT=22', 'TERMINAL_SSH_KEY="C:/Users/Lab User/.ssh/ubuntu_lab"')Set-Content -Path $envFile -Value $lines -Encoding utf8NoBOMSelect-String -Path $envFile -Pattern '^TERMINAL_SSH_' | ForEach-Object LineThe quotes around the key path are required when your Windows account folder contains a space, as Lab User does. Use forward slashes and the private key. A Kali profile stays untouched: hermes -p kali-lab ... still targets the Kali laptop.
5.3 Restart the gateway and test
Section titled “5.3 Restart the gateway and test”hermes gateway restart # "Gateway started via direct spawn"; profile list shows ubuntu-lab "running"hermes profile listhermes -p ubuntu-lab chat -Q -q "Use your terminal tool to run: hostname && whoami && pwd && grep PRETTY_NAME /etc/os-release. Reply with only the output."Expected output:
lab-Ubuntulabuser/home/labuserPRETTY_NAME="Ubuntu 26.04.1 LTS"In Hermes Desktop:
- In the Sessions sidebar, select the gateway This device.
- In the profile rail, select ubuntu-lab.
- Ask the same question, and expect the same output.
The profiles side by side:
| Profile | Commands run on |
|---|---|
default |
Windows |
kali-lab |
Kali (192.168.50.20), from the Kali tutorial |
ubuntu-lab |
Ubuntu (192.168.50.21) |
Desktop window backend: %APPDATA%\Hermes\connection.json holds the connection mode that Desktop’s window boots into. If it points over SSH at another laptop, for example Kali’s Hermes from the Kali tutorial (its section B.5), Desktop fails at launch whenever that laptop is off, with Desktop boot failed: Could not reach ... over SSH. For everyday use of ubuntu-lab, set Settings → Gateways → connection mode to Local / This device. Kali’s own Hermes stays reachable through its Kali Lab gateway entry.
Phase 6: GNOME Remote Desktop (Desktop Sharing + Remote Login)
Section titled “Phase 6: GNOME Remote Desktop (Desktop Sharing + Remote Login)”6.1 Key facts before you start
Section titled “6.1 Key facts before you start”- Both modes speak RDP, so Windows uses its built-in Remote Desktop Connection (
mstsc). No extra software is needed. - Each mode has its own RDP username and password, set in Settings → System → Remote Desktop. They are not your Linux password and not your Windows/Microsoft account. This tutorial uses the username
labuserfor both modes; use the same password for both (6.6). - Remote Login adds a second step: after the RDP login, Ubuntu’s own login screen (GDM) appears, and you sign in there with the Linux password.
- Ports: Desktop Sharing 3390 and Remote Login 3389 when both are on.
6.2 Desktop Sharing (the real screen)
Section titled “6.2 Desktop Sharing (the real screen)”On the Ubuntu laptop:
- Open Settings → System → Remote Desktop → Desktop Sharing.
- Turn on Desktop Sharing and Remote Control (Remote Control lets you use mouse and keyboard, not just watch).
- Under Login Details, set the username (
labuserin this tutorial) and a password.
Verify (as labuser, in a local terminal or over SSH):
export XDG_RUNTIME_DIR=/run/user/$(id -u) DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus # only needed over SSHgrdctl status # RDP Status: enabled, View-only: no, Negotiate port: yes, Username/Password: (hidden)gsettings get org.gnome.desktop.remote-desktop.rdp screen-share-mode # 'mirror-primary'systemctl --user is-active gnome-remote-desktop # activess -ltn | grep -E ':(3389|3390)\b'Expected:
- A TLS certificate at
~/.local/share/gnome-remote-desktop/certificates/rdp-tls.crt. - Port 3390 once Remote Login holds 3389.
grdctl status keeps showing the configured Port: 3389. The actual port is in ss (Negotiate port: yes lets it move).
If logins fail with SEC_E_NO_CREDENTIALS in the log (Phase 10), the running daemon has no login loaded. Fix:
-
Switch Desktop Sharing off and on and re-type the password under Login Details.
-
Run
systemctl --user restart gnome-remote-desktop. -
Check it’s loaded (prints set/empty only, never the value):
grdctl status --show-credentials | sed -E 's/^(\s*(Username|Password):)\s+\S.*$/\1 <set>/'
--show-credentials goes after status; grdctl --show-credentials status only prints usage.
6.3 Remote Login (a separate session)
Section titled “6.3 Remote Login (a separate session)”On the Ubuntu laptop, open Settings → System → Remote Desktop → Remote Login → Unlock (admin password), switch Remote Login on, and fill Login Details (username labuser + password).
Settings can display the Remote Login details without delivering them to the service. The system daemon then keeps logging [RDP] Credentials are not set, denying client, and Windows shows error 0x904 / 0x7. The reliable way is to store them with grdctl --system and restart.
In an SSH or local terminal on Ubuntu:
sudo grdctl --system rdp set-credentials # prompts: "Username:" then "Password:" (nothing on the command line)sudo systemctl daemon-reload # clears "unit file ... changed on disk" warningssudo systemctl restart gnome-remote-desktopsudo grdctl --system status | grep -E 'Status|Port|Username' # Status: enabled / Port: 3389 / Username: (hidden)Notes:
Init TPM credentials failed because No TPM device found, using GKeyFile as fallbackis informational. On a laptop without a TPM chip, the login is stored in a root-only key file under/var/lib/gnome-remote-desktop.- If you must pass the values as arguments, start the line with a space. Ubuntu’s default
HISTCONTROL=ignoreboththen keeps it out of bash history:Never type such a command in Windows PowerShell (PSReadLine saves history), and never put the password in a screenshot or chat. If that happens, set a new password.sudo grdctl --system rdp set-credentials labuser 'NEW_PASSWORD' - Each successful connection is logged by the system daemon as
[RDP] Sending server redirection, followed bygnome-remote-desktop-handover.servicestarting in the session. Read it withjournalctl -u gnome-remote-desktop(no sudo needed for a member ofadm).
6.4 What happens on connect
Section titled “6.4 What happens on connect”Ubuntu Screen (Desktop Sharing): RDP login → Ubuntu’s live screen, scaled to the window. If the screen is locked, you see the lock screen; unlock it with the Linux password.
Ubuntu Login (Remote Login):
- RDP login.
- Ubuntu’s login screen (GDM) appears inside the window.
- Sign in to your account (GDM shows its display name) with the Linux password.
- If you are logged in on the laptop, Ubuntu offers to end that local session and continue remotely. Accepting it also closes any open Ubuntu Screen window, because that local session is the one being shared.
Use Ubuntu Screen while you are logged in at the laptop, and Ubuntu Login when nobody is.
6.5 Windows connection files and desktop shortcuts
Section titled “6.5 Windows connection files and desktop shortcuts”Two RDP connection files hold the settings. Desktop shortcuts with an Ubuntu icon open them.
C:\RemoteAccess\Ubuntu-Screen.rdp (Desktop Sharing):
full address:s:192.168.50.21:3390username:s:labuserscreen mode id:i:1smart sizing:i:1dynamic resolution:i:0prompt for credentials on client:i:1authentication level:i:2smart sizing:i:1scales Ubuntu’s 4K screen to fit any window size; it’s a mirror, so its resolution can’t change.username:s:labuserstops Windows from pre-filling your Microsoft account.authentication level:i:2warns on the self-signed certificate instead of refusing it.
C:\RemoteAccess\Ubuntu-Login.rdp (Remote Login):
full address:s:192.168.50.21:3389username:s:labuserscreen mode id:i:2use multimon:i:0dynamic resolution:i:1smart sizing:i:0desktopscalefactor:i:200prompt for credentials on client:i:1authentication level:i:2screen mode id:i:2opens full screen on one monitor.dynamic resolution:i:1makes Ubuntu re-fit its desktop whenever the window size changes.desktopscalefactor:i:200asks Ubuntu to draw at 200 %, to match a Windows display at 200 %.
Without the size settings, the Remote Login window opens tiny (about 430×340), with content at 100 % scale. If text is still small with the settings above, set Settings → Displays → Scale → 200 % inside the remote session.
Create both files and shortcuts with the commands in Appendix B.
6.6 How Windows remembers these logins
Section titled “6.6 How Windows remembers these logins”Windows saves remembered RDP logins per host, not per port. The target is TERMSRV/192.168.50.21, the same for 3389 and 3390; a Kali laptop from the other tutorial would have its own entry, TERMSRV/192.168.50.20. So both Ubuntu shortcuts share one remembered login.
Give Desktop Sharing and Remote Login the same username and password, and tick Remember me once. Otherwise leave Remember me unticked, or each shortcut will overwrite the other’s saved password.
List or remove saved entries (target names only):
(cmdkey /list) | Select-String 'Target:.*TERMSRV'cmdkey /delete:TERMSRV/192.168.50.21Phase 7: Keep the Ubuntu laptop reachable
Section titled “Phase 7: Keep the Ubuntu laptop reachable”Remote access stops if the laptop suspends. Check these settings (as labuser):
gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type # 'nothing'gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type # 'nothing'gsettings get org.gnome.desktop.session idle-delay # uint32 0 (screen never blanks)gsettings get org.gnome.desktop.screensaver lock-enabled # true (locks when blanked)In the GUI: Settings → Power → Automatic Suspend: Off. If you change these, Desktop Sharing still works on a locked screen (you unlock it remotely). A suspended laptop needs to be woken locally.
Optional, for long jobs over SSH (tmux keeps them running if SSH drops):
ssh -t ubuntu-lab "sudo apt install -y tmux"Phase 8: Security boundaries
Section titled “Phase 8: Security boundaries”- LAN only. Do not port-forward 22, 3389, or 3390 on the router. Use a VPN for outside access.
- Keys:
ubuntu_labstays on Windows; Ubuntu holds oneauthorized_keysline (mode 600). Revoke Windows’ access by deleting that line. - Host key: stop if Ubuntu’s SSH fingerprint ever changes from the one you recorded in Phase 3.
- RDP passwords:
- They are stored in your account’s keyring (Desktop Sharing) and in a root-only file (Remote Login).
- Use strong, unique values.
- Change them with Settings (Desktop Sharing) or
sudo grdctl --system rdp set-credentials(Remote Login). - Never share them in screenshots or chats; rotate them if that happens.
- Certificates: both RDP modes use self-signed TLS certificates. Accept the prompt only on your home network, and tick “Don’t ask me again” for this host once verified.
- Hermes:
- Profile
ubuntu-labacts aslabuser(an administrator). - Keep approvals on, and never store the sudo password.
- Keep
%LOCALAPPDATA%\hermes\profiles\ubuntu-lab\.envprivate. - The SSH backend copies some Hermes cache folders (documents, images, attachments) to Ubuntu during sessions; treat Ubuntu as trusted.
- Profile
Phase 9: Verify the complete workflow
Section titled “Phase 9: Verify the complete workflow”# Network and portsTest-NetConnection 192.168.50.21 -Port 22 # TrueTest-NetConnection 192.168.50.21 -Port 3389 # True (Remote Login)Test-NetConnection 192.168.50.21 -Port 3390 # True (Desktop Sharing)
# SSH (no prompts)ssh -o BatchMode=yes ubuntu-lab "hostname && whoami && grep PRETTY_NAME /etc/os-release"
# Hermes Mode Ahermes -p ubuntu-lab config get terminal.backend # sshhermes -p ubuntu-lab chat -Q -q "Use your terminal tool to run: hostname && whoami. Reply with only the output."Then:
- Ubuntu Screen: while you are logged in at the laptop, double-click it and sign in as
labuserwith the RDP password. You see the live screen. - Ubuntu Login: while you are logged out at the laptop (or accept “end session”), double-click it and sign in as
labuserwith the RDP password, then with the Linux password at GDM. You get a separate session.
Phase 10: Troubleshooting
Section titled “Phase 10: Troubleshooting”10.1 Common errors, with cause and fix
Section titled “10.1 Common errors, with cause and fix”| Symptom / exact message | Cause | Fix |
|---|---|---|
lab-ubuntu.local → 192.168.50.20; port 22 refused at that address |
Router DHCP gave Ubuntu Kali’s static address (Kali off; the address not reserved in the router) | Give Ubuntu its own static address (Phase 1.3) |
TcpTestSucceeded : False on port 22 / connection refused |
Ubuntu Desktop has no SSH server by default | sudo apt install -y openssh-server && sudo systemctl enable --now ssh |
Are you sure you want to continue connecting (yes/no/[fingerprint])? then Host key verification failed. while piping the key |
The yes/no prompt read the piped key text | Pass the key inside the command (4.4), or record the host key first (4.2) |
choose_kex: unsupported KEX method sntrup761x25519-sha512@openssh.com from ssh-keyscan |
Windows OpenSSH 9.5 ssh-keyscan limitation |
Capture the host key with ssh and a temporary known_hosts file (4.2) |
ssh lab-Ubuntu: The authenticity of host 'lab-ubuntu (fe80::...%11)' can't be established |
The hostname isn’t an alias; mDNS resolved it to IPv6 link-local | Add lab-ubuntu lab-Ubuntu to the Host line (4.3) |
authorized_keys has the key twice |
Install command run twice | awk '!seen[$0]++' dedupe (4.4) |
ssh-keygen creates a key with a passphrase, or fails on -N '' |
Windows PowerShell 5.1 drops the empty argument | Run the command in PowerShell 7.3 or later (0.1) |
hermes profile create: “This profile has no API keys yet” |
Expected for sign-in (OAuth) providers such as Nous Portal | Ignore; test with the one-shot chat (5.3) |
Desktop: Desktop boot failed: Could not reach 192.168.50.20 over SSH |
Desktop’s window backend is SSH → Kali, and Kali is off | Connection mode Local / This device (5.3) |
| Desktop shortcut “not visible” | Explorer has not refreshed | Click the desktop and press F5 (the file is in your Desktop folder) |
Windows Security: “Your credentials did not work”, user pre-filled MicrosoftAccount\… |
The Remote Desktop app defaults to your Windows account | More choices → Use a different account → labuser + RDP password; the username:s:labuser line pre-sets it |
Desktop Sharing log: AcceptSecurityContext status SEC_E_NO_CREDENTIALS / client authentication failure |
The running user daemon had no RDP login loaded | Re-enter Login Details (toggle off/on), systemctl --user restart gnome-remote-desktop (6.2) |
grdctl --show-credentials status prints only Usage: ... |
Option in the wrong position | grdctl status --show-credentials |
| 0x904, extended 0x7 “This computer can’t connect to the remote computer” on 3389 after enabling Remote Login | Remote Login took 3389 and Desktop Sharing moved to 3390; the shortcut still targeted 3389 | Ubuntu Screen → 192.168.50.21:3390 (6.5) |
0x904 / 0x7 on Ubuntu Login; system log [RDP] Credentials are not set, denying client |
Settings showed Remote Login details but never stored them for the system daemon | sudo grdctl --system rdp set-credentials + restart (6.3) |
Init TPM credentials failed because No TPM device found, using GKeyFile as fallback |
No TPM chip; informational | None |
Warning: The unit file ... of gnome-remote-desktop.service changed on disk. Run 'systemctl daemon-reload' |
Unit changed when Remote Login was enabled | sudo systemctl daemon-reload && sudo systemctl restart gnome-remote-desktop |
| Ubuntu Login opens as a tiny window with small content | No size or scale in the RDP file | Full screen + dynamic resolution + desktopscalefactor:i:200 (6.5) |
| One shortcut’s saved password breaks the other | Windows saves RDP logins per host (TERMSRV/192.168.50.21), not per port |
Same login for both modes, or don’t tick Remember me (6.6) |
10.2 General checks
Section titled “10.2 General checks”| Symptom | Check |
|---|---|
| Can’t reach Ubuntu at all | Laptop on and awake? ping 192.168.50.21; nmcli -g ipv4.addresses connection show HomeWiFi |
| SSH works, Hermes fails | hermes -p ubuntu-lab config get terminal.backend; the profile’s .env (quoted key path); SSH penalty after failed logins |
| Hermes ran commands on the wrong machine | Selected profile: ubuntu-lab on gateway This device |
| Desktop Sharing shows the lock screen | Normal when locked; unlock with the Linux password |
| Ubuntu Screen fails, Ubuntu Login works (or vice versa) | ss -ltn | grep -E ':(3389|3390)' on Ubuntu; check which port each .rdp uses |
10.3 Where the logs are
Section titled “10.3 Where the logs are”| Machine | Log | Command |
|---|---|---|
| Ubuntu | Desktop Sharing (user daemon) | journalctl --user -u gnome-remote-desktop --since "-30 min" |
| Ubuntu | Remote Login (system daemon) | journalctl -u gnome-remote-desktop --since "-30 min" (no sudo for a member of adm) |
| Ubuntu | Handover / GDM | journalctl --since "-30 min" | grep -iE 'handover|gdm' |
| Ubuntu | SSH | journalctl -u ssh -n 50 --no-pager |
| Windows | RDP client | Event Viewer → Applications and Services Logs → Microsoft → Windows → TerminalServices-RDPClient → Operational |
| Windows | Hermes Desktop | %LOCALAPPDATA%\hermes\logs\desktop.log |
Filter the noisy FreeRDP lines when reading GNOME Remote Desktop logs:
journalctl -u gnome-remote-desktop --since "-30 min" --no-pager | grep -vE 'winpr|freerdp'journalctl --user -u gnome-remote-desktop --since "-30 min" --no-pager | grep -oE 'SEC_E_[A-Z_]+|Credentials are not set|Sending server redirection' | sort | uniq -cWindows RDP client events from the last 30 minutes:
Get-WinEvent -FilterHashtable @{ LogName='Microsoft-Windows-TerminalServices-RDPClient/Operational'; StartTime=(Get-Date).AddMinutes(-30) } | Sort-Object TimeCreated | Select-Object TimeCreated, Id, @{n='Msg';e={($_.Message -replace '\s+',' ').Substring(0,[Math]::Min(140,($_.Message -replace '\s+',' ').Length))}}Appendix A: Configuration reference
Section titled “Appendix A: Configuration reference”| Where | What |
|---|---|
Ubuntu NetworkManager HomeWiFi |
ipv4.method manual, 192.168.50.21/24, gw/DNS 192.168.50.1, autoconnect yes |
Ubuntu ~/.ssh/authorized_keys |
1 line: ssh-ed25519 … lab user@winpc ubuntu_lab; ~/.ssh 700, the file 600 |
| Ubuntu GNOME Remote Desktop (user) | RDP enabled, mirror-primary, view-only no, negotiate port yes → listening 3390, username labuser |
| Ubuntu GNOME Remote Desktop (system) | enabled, port 3389, username labuser, credentials in GKeyFile (no TPM) |
| Ubuntu power | suspend nothing (AC and battery), idle-delay 0 |
Windows ~\.ssh\config |
Host ubuntu-lab lab-ubuntu lab-Ubuntu 192.168.50.21 block (4.3) |
Windows ~\.ssh\known_hosts |
192.168.50.21 ssh-ed25519 … (the fingerprint you recorded in Phase 3) |
Windows Hermes profile ubuntu-lab |
terminal.backend: ssh; .env: TERMINAL_SSH_HOST=192.168.50.21, USER=labuser, PORT=22, KEY="C:/Users/Lab User/.ssh/ubuntu_lab" |
| Windows RDP files | C:\RemoteAccess\Ubuntu-Screen.rdp (3390), C:\RemoteAccess\Ubuntu-Login.rdp (3389), contents in 6.5 |
| Windows shortcuts | Ubuntu Screen.lnk and Ubuntu Login.lnk in your Desktop folder → mstsc.exe "<rdp>", icon C:\RemoteAccess\ubuntu.ico |
Appendix B: Create the RDP files and desktop shortcuts (Ubuntu icon)
Section titled “Appendix B: Create the RDP files and desktop shortcuts (Ubuntu icon)”Any .ico file works for the icon. Download an Ubuntu icon from a source whose license lets you use it, and save it as C:\RemoteAccess\ubuntu.ico.
B.1 With PowerShell (normal user)
Section titled “B.1 With PowerShell (normal user)”# 0. A folder for the RDP files and the iconNew-Item -ItemType Directory -Force 'C:\RemoteAccess' | Out-Null
# 1. RDP connection files (Unicode encoding, as mstsc writes them)@'full address:s:192.168.50.21:3390username:s:labuserscreen mode id:i:1smart sizing:i:1dynamic resolution:i:0prompt for credentials on client:i:1authentication level:i:2'@ | Set-Content 'C:\RemoteAccess\Ubuntu-Screen.rdp' -Encoding Unicode
@'full address:s:192.168.50.21:3389username:s:labuserscreen mode id:i:2use multimon:i:0dynamic resolution:i:1smart sizing:i:0desktopscalefactor:i:200prompt for credentials on client:i:1authentication level:i:2'@ | Set-Content 'C:\RemoteAccess\Ubuntu-Login.rdp' -Encoding Unicode
# 2. Desktop shortcuts with the Ubuntu icon$desk = [Environment]::GetFolderPath('Desktop'); $sh = New-Object -ComObject WScript.Shellforeach ($s in @( @{ Name = 'Ubuntu Screen'; Rdp = 'C:\RemoteAccess\Ubuntu-Screen.rdp'; Desc = "Ubuntu laptop's real screen (GNOME Desktop Sharing, RDP port 3390)" }, @{ Name = 'Ubuntu Login'; Rdp = 'C:\RemoteAccess\Ubuntu-Login.rdp'; Desc = 'Ubuntu laptop: separate session (GNOME Remote Login, RDP port 3389)' })) { $l = $sh.CreateShortcut("$desk\$($s.Name).lnk") $l.TargetPath = "$env:SystemRoot\System32\mstsc.exe"; $l.Arguments = "`"$($s.Rdp)`""; $l.WorkingDirectory = 'C:\RemoteAccess' $l.IconLocation = 'C:\RemoteAccess\ubuntu.ico,0'; $l.Description = $s.Desc; $l.Save()}
# 3. Make Explorer show them immediately (same as pressing F5 on the desktop)Add-Type -Namespace Shell32 -Name Notify -MemberDefinition '[DllImport("shell32.dll")] public static extern void SHChangeNotify(int e, uint f, System.IntPtr a, System.IntPtr b);'[Shell32.Notify]::SHChangeNotify(0x08000000, 0, [IntPtr]::Zero, [IntPtr]::Zero)B.2 By hand
Section titled “B.2 By hand”- Open Remote Desktop Connection (
mstsc), then Show Options:- General: Computer
192.168.50.21:3390, User namelabuser. - Save As
C:\RemoteAccess\Ubuntu-Screen.rdp. - Smart sizing isn’t in this dialog. Open the saved file in Notepad and add the line
smart sizing:i:1.
- General: Computer
- Repeat for
192.168.50.21:3389with Display → Full screen, saved asC:\RemoteAccess\Ubuntu-Login.rdp. Add thedynamic resolutionanddesktopscalefactorlines in Notepad. - Right-click the desktop → New → Shortcut → location
C:\Windows\System32\mstsc.exe "C:\RemoteAccess\Ubuntu-Screen.rdp"→ name Ubuntu Screen. - Properties → Change Icon… → Browse →
C:\RemoteAccess\ubuntu.ico. Repeat for Ubuntu Login withC:\RemoteAccess\Ubuntu-Login.rdp. - If the icons don’t appear, press F5 on the desktop. If an icon stays stale, run
ie4uinit.exe -show.
Appendix C: Ubuntu vs Kali at a glance
Section titled “Appendix C: Ubuntu vs Kali at a glance”| Item | Kali (Kali tutorial) | Ubuntu (this tutorial) |
|---|---|---|
| Address | 192.168.50.20 static |
192.168.50.21 static |
| SSH alias / key | kali-lab / kali_lab |
ubuntu-lab (+ lab-ubuntu, lab-Ubuntu) / ubuntu_lab |
| Shell | zsh (needs ~/.zshenv for Mode B) |
bash |
| Hermes | Mode A (kali-lab) + Mode B (Kali Lab gateway) |
Mode A only (ubuntu-lab) |
| Separate desktop | XRDP + Xfce on 3389, login with the Linux password; coexists with a local login (private D-Bus fix) | GNOME Remote Login on 3389, RDP password, then the Linux password at GDM; ends a local login if one exists |
| Real screen | x11vnc + TigerVNC via SSH tunnel (Open-KaliScreen.ps1) |
GNOME Desktop Sharing on 3390 via mstsc |
| Scaling fix | x11vnc -scale fit (TigerVNC is DPI-unaware) |
smart sizing (Screen); dynamic resolution + desktopscalefactor (Login) |
| Desktop shortcuts | Kali Screen | Ubuntu Screen, Ubuntu Login |
Final checklist
Section titled “Final checklist”The setup is complete when three things work:
ssh ubuntu-labworks without prompts.- Profile
ubuntu-labreturnslab-Ubuntu/labuser. - Both Ubuntu Screen and Ubuntu Login connect with the RDP login.
Official documentation
Section titled “Official documentation”- Ubuntu: Share your desktop remotely: Desktop Sharing vs Remote Login, credentials, ports 3389/3390, firewall.
- Ubuntu: Access a remote desktop
- GNOME Remote Desktop project:
grdctl, headless/system daemon. - Launchpad bug 2141992: a related Remote Login handover issue on 25.10 (GNOME 49), and the source of the
grdctl --systemworkaround. - Hermes Agent configuration: SSH backend
- Hermes Agent profiles
- Microsoft: Supported RDP properties:
smart sizing,dynamic resolution,desktopscalefactor,screen mode id,username. - Companion tutorial: Control a Kali Linux Laptop from Windows 11.