Skip to main content
PathMon

Chapter 21 of 26

Web SSH Terminal

Updated Read the full guide

Overview

PatchMon ships an in-browser SSH terminal that lets operators connect to any monitored Linux, FreeBSD or Windows host without leaving the web UI. Windows hosts need the OpenSSH Server feature enabled first; see Windows hosts. The terminal is a full xterm with line editing, colours, scrollback, resize, and keyboard shortcuts, powered by a WebSocket between the browser and the PatchMon server.

Two connection modes are supported:

  • Direct: the PatchMon server dials the host's SSH port (22 by default) and bridges the session. Use this when your server has network reach to the hosts.
  • Proxy: the PatchMon server asks the host's own agent to open a local SSH connection (to localhost:22 on the host) and tunnels it back through the agent's existing outbound WebSocket. No inbound SSH port exposure required on the target host.

Authentication to the host uses an SSH password or an SSH private key (with an optional passphrase). Authentication to PatchMon itself is handled by your existing session cookies plus a one-time ticket described below.

Web SSH is shipped in PatchMon from 1.4.0 onwards; in 2.0 it is provided under the remote_access capability module.

Permissions

Role Web SSH access
admin / superadmin Always granted.
Any other role Requires can_use_remote_access on the role permissions.

Users without can_use_remote_access attempting to open the terminal are rejected with HTTP 403 Access denied during the WebSocket handshake.

The Terminal tab on the host detail page is only shown to users whose role has can_use_remote_access.

Opening a terminal

  1. Go to Hosts and click the host you want to connect to.
  2. On the Host Detail page, open the Terminal tab (or click the SSH Terminal button in the header).
  3. Pick the Connection mode (Direct or Proxy).
  4. Enter the SSH username (defaults to root, or Administrator on Windows hosts; the last-used username per host is cached in your browser's local storage and takes precedence).
  5. Choose an Authentication method:
    • Password: type the host password.
    • Key: paste the private key (OpenSSH or PEM format) and the passphrase if encrypted.
  6. Adjust the SSH port if needed (default 22).
  7. If you picked Proxy mode, set the Proxy host (default localhost) and Proxy port (default 22). These are the destination the agent will dial, typically localhost:22 when you want the agent to SSH into its own host.
  8. Click Connect.

Once the green "SSH connection established" line appears, the terminal is live and interactive.

Your SSH credentials are never stored by the server or browser. They are sent over the authenticated WebSocket once at connect time and held in browser state only for the life of the session. Disconnecting clears them from memory.

Direct mode

In Direct mode, the PatchMon server dials the host directly:

  1. Browser → POST /api/v1/auth/ssh-ticket with { "hostId": "<id>" }. Requires your PatchMon session cookie. Returns a 30-second, single-use ticket.
  2. Browser opens wss://<patchmon-host>/api/v1/ssh-terminal/<hostId>?ticket=<ticket>.
  3. Server consumes the ticket (deleted from Redis on use), validates the user is active and has permission, and upgrades to WebSocket.
  4. Browser sends the connect message with auth credentials, terminal size, and connection mode.
  5. Server dials host.ip (falling back to host.hostname) on the chosen port, authenticates with password or private key, and starts an interactive shell.

Host key verification (trust on first use): host keys are now always verified against a PatchMon-managed known_hosts file. The first time a host is contacted its key is recorded and the connection is accepted; on later connections a key that no longer matches is rejected as a possible man-in-the-middle and the session fails. The insecure "accept any key" behaviour has been removed. In the stock Docker image this file is not on a persistent volume, so recorded keys reset when the container is recreated and the next connection re-records them. To keep keys across upgrades, set PATCHMON_SSH_KNOWN_HOSTS to a path on a mounted volume.

Use Direct mode when:

  • The PatchMon server has network reach to the host on the SSH port.
  • You accept bridging SSH via the server host rather than via the host's agent.

Proxy mode

Proxy mode routes the SSH session through the host's existing agent WebSocket, avoiding the need to expose an SSH port inbound to PatchMon.

Flow:

  1. Browser → ticket + WebSocket as in Direct mode.
  2. Server receives the connect message with connection_mode: "proxy".
  3. Server generates a 16-byte session ID, stores a proxy session record, and sends { "type": "ssh_proxy", "session_id": …, "host": "localhost", "port": 22, "username": … } over the agent's existing WebSocket.
  4. The agent dials <proxy_host>:<proxy_port> (defaults localhost:22) on its own host and pipes the stream back to the server over the WebSocket as ssh_proxy_data frames. For security, proxy_host is restricted to the host's own registered address (its IP, hostname or interface address) or localhost; it cannot be used to reach other machines through the agent.
  5. The server forwards those frames to the browser as terminal data events.

Agent config requirement. Proxy mode requires integrations.ssh-proxy-enabled: true in the agent's /etc/patchmon/config.yml. This setting is not pushed from the server. It has to be set manually and the agent service restarted. If the agent rejects the request, the terminal shows "Agent not connected" or an agent-supplied error.

Use Proxy mode when:

  • The host has no inbound SSH exposure (behind NAT, in a restricted VPC, behind a corporate firewall).
  • You already trust the agent's outbound connection to PatchMon and want to reuse it.
  • You want to SSH to localhost through the agent without punching holes through the edge firewall.

Windows hosts

The Terminal tab works on Windows hosts through the OpenSSH Server that ships with Windows. PatchMon does not install or enable it for you. On Windows hosts the tab shows a short reminder of this above the connection form.

Enable OpenSSH Server. Run in an elevated PowerShell on the host:

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

If the feature is already installed, the first command changes nothing; you still need to start the service and set it to start automatically.

Allow port 22. Installing the feature normally creates the OpenSSH-Server-In-TCP firewall rule. Check, and create it if it is missing:

if (-not (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
}

The firewall rule matters for Direct mode, where the PatchMon server dials the host. Proxy mode connects to localhost:22 from the agent on the host itself, so it needs only the service running and ssh-proxy-enabled: true in C:\ProgramData\PatchMon\config.yml.

Username. On Windows hosts the username defaults to Administrator. Any local administrator or domain account allowed to log on works; for a domain account use DOMAIN\user or user@domain. On Windows client editions (Windows 10 and 11) the built-in Administrator account is disabled by default, and on non-English installs it has a localised name (for example Administrateur), so sign in with your own administrator account instead. A username you have used before on that host is remembered and takes precedence.

Key authentication. For an account in the local Administrators group, Windows OpenSSH ignores the user's own ~\.ssh\authorized_keys and reads C:\ProgramData\ssh\administrators_authorized_keys instead. The file must be writable only by Administrators and SYSTEM, or sshd silently refuses the key:

Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value "ssh-ed25519 AAAA... you@example"
icacls.exe C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"

Accounts that are not administrators use C:\Users\<user>\.ssh\authorized_keys as usual. Paste the matching private key into the SSH Key field in the Terminal tab.

Shell. The session opens in cmd.exe, the Windows OpenSSH default. Type powershell to switch for the rest of the session. To change the default for every SSH login on the host, set the DefaultShell value under HKLM:\SOFTWARE\OpenSSH on the host; PatchMon uses whatever shell sshd starts.

Terminal or RDP? The Terminal tab gives you a command line over SSH and works with password or key authentication. The RDP tab gives you the full Windows desktop through Guacamole and needs rdp-proxy-enabled on the agent instead. Use the terminal for quick commands and scripts, and RDP when you need the GUI.

One-time tickets for WebSocket auth

WebSocket upgrades cannot include the normal authentication cookies reliably across all browsers, and passing long-lived tokens via query parameters would expose them in server logs and browser history. PatchMon avoids both problems with one-time tickets:

  • Tickets are 64 hex characters, generated from crypto/rand on the server.
  • Tickets live in Redis with a 30-second TTL.
  • A ticket carries the user ID and the host ID it was minted for.
  • The WebSocket handler consumes the ticket on first use (atomic DEL). A second attempt to open a WebSocket with the same ticket fails with Invalid or expired ticket.
  • Ticket validation also verifies the hostId in the URL matches the one encoded in the ticket. Stolen tickets cannot be reused against a different host.

You get the ticket implicitly by clicking Connect in the UI; there is no operator-visible ticket string.

Keyboard and terminal interactions

The embedded xterm supports the usual shortcuts:

Action Shortcut
Copy selection Browser-standard (Ctrl+Shift+C / Cmd+C)
Paste Ctrl+Shift+V / Cmd+V
Send Ctrl+C to the remote Ctrl+C (when no selection)
Scrollback Mouse wheel or trackpad
Clear screen Remote clear command

The terminal automatically resizes when the PatchMon browser window resizes, the AI Assistant panel opens/closes, or the sidebar collapses. The server is notified over the WebSocket so the remote TTY keeps cols and rows in sync. Resize events are honoured in Direct mode (when supported by the remote SSH server) and in Proxy mode via ssh_proxy_resize messages to the agent.

Terminal output is also captured in a rolling 5 000-character buffer for the AI Terminal Assistant, if enabled.

Session lifetime and idle timeout

  • Ticket TTL: 30 seconds. A session that takes longer than that to start must re-request a ticket.
  • Idle disconnect: after 15 minutes with no terminal activity the session is closed automatically. A visible warning appears 1 minute before the disconnect. Any input or output resets the timer.
  • Manual disconnect: click Disconnect in the toolbar or close the terminal panel. Credentials are wiped from browser state on disconnect.

What happens when the WebSocket drops

  • If the server-side SSH process exits (e.g. you type exit on the remote shell), the terminal shows "SSH connection closed" and the WebSocket stays open for a potential new connect.
  • If the WebSocket itself drops unexpectedly and you were connected, the terminal attempts to reconnect once after 3 seconds, via a brand-new ticket and WebSocket. Authentication re-uses the cached username but you must re-enter password or key, as credentials are not persisted in the browser.
  • Close codes that are not retried: 1000 (normal close), 1006 (abnormal close, often auth failure), 1008 (policy violation). For those, you get "Connection failed: Session may have expired. Please refresh the page or log in again.".

Auditing

Every successful ticket mint (i.e. the user has requested a terminal session) fires an ssh_session_started event:

  • Severity: informational (configurable in Alert Lifecycle).
  • Metadata: host_id, host_name, user_id.
  • Reference: the host record.

Route this event type to a destination (for example, a #security Discord channel) if you want a live audit trail of all web SSH sessions. Configure routing in Notification Routes and Delivery Log.

Server logs also record each upgrade and ticket consumption under ssh-terminal connected and ssh-terminal ticket invalid log lines.

Troubleshooting

Symptom Likely cause and fix
"Authentication required. Please log in again." when clicking Connect Your PatchMon session cookie is missing or expired. Refresh the page and sign in.
"Invalid or expired ticket" on upgrade More than 30 seconds elapsed between ticket mint and WebSocket open, or the ticket was already consumed. Retry; PatchMon mints a new ticket on the retry.
"Agent not connected." in Proxy mode Host's agent WebSocket is down. Verify from Host Detail → Status; restart the agent service on the host.
Agent rejects with "ssh-proxy-enabled must be true" Set integrations.ssh-proxy-enabled: true in the agent's config.yml and restart the agent service.
"Failed to parse private key" Key is encrypted: add the passphrase. Or the key format is unsupported; use OpenSSH or PEM PKCS#8.
Windows host: "connection refused" or a timeout OpenSSH Server is not installed or sshd is stopped, or (Direct mode) port 22 is blocked by the Windows firewall. See Windows hosts.
Windows host: key is rejected but the password works The account is a local administrator, so sshd reads C:\ProgramData\ssh\administrators_authorized_keys, not the user's own file. Check that file and its permissions.
"host key verification failed … (possible man-in-the-middle)" The host presented a different SSH key than the one recorded on first use. If the host was legitimately rebuilt or its key rotated, remove its line from the PatchMon-managed known_hosts file and reconnect to record the new key.
"Proxy host must be the host's own address or localhost" In Proxy mode the agent will only connect to the host's own registered address or localhost. Connecting to an unrelated address through the agent is blocked by design.