Chapter 8 of 15
Installing the PatchMon Agent
Overview
The PatchMon agent is enrolled through the web UI's Add Host wizard. The wizard provisions a host record in the database, issues a one-time bootstrap token, and builds a ready-to-paste one-liner. Running that command on the target host downloads the platform-specific agent binary over an authenticated HTTPS channel, writes config and credentials, registers a system service, and opens a persistent WebSocket connection back to the server.
This page covers the UI-driven install flow end to end. For everything that happens after the first check-in (CLI commands, service management, logs, updates), see Managing the PatchMon Agent. For bulk enrolment on container hosts, see Proxmox LXC Auto-Enrollment Guide.
How it Works
- The web UI calls
POST /api/v1/hosts(admin-authenticated) to create a host row and returns a plaintextapi_id/api_keypair. This is the only time the plaintext key is ever exposed. - The wizard builds an install URL (
GET /api/v1/hosts/install?os=<linux|freebsd|windows>) and pre-shares the credentials viaX-API-ID/X-API-KEYheaders. - The server responds with an OS-specific install script. A short-lived bootstrap token (5-minute TTL, single-use) is embedded at the top of the script so the host can exchange it for its permanent
api_id/api_keyviaPOST /api/v1/hosts/bootstrap/exchange. - The installer auto-detects architecture (via
uname -morPROCESSOR_ARCHITECTURE), downloads the matching agent binary fromGET /api/v1/hosts/agent/download, writes/etc/patchmon/config.ymland/etc/patchmon/credentials.yml(or the Windows equivalents), and starts the service. - On first
serve, the agent opens a WebSocket to/api/v1/agents/wsand sends an initial report. The wizard polls/api/v1/ws/status/{apiId}every 2 seconds and moves through four states: Waiting for connection → Connected → Receiving initial report → Done. It then redirects you to the host detail page.
Prerequisites
Before you enrol a host, make sure:
- You can log into PatchMon as a user with the
can_manage_hostspermission (admin, superadmin, or a custom role with that permission). - The target host can reach the PatchMon server over HTTPS (TCP/443 or whatever port your reverse proxy exposes).
- The target host's clock is correct. The installer checks this and will warn (or abort interactively) if the system time looks wrong. TLS and signed tokens will both fail silently if the clock drifts more than a few minutes.
- On Linux / FreeBSD you have
rootaccess (orsudo). On Windows you have an elevated PowerShell (Run as Administrator).
Step-by-Step Walkthrough
Step 1: Open the Add Host Wizard
Navigate to Hosts → Add Host in the web UI. A four-step wizard appears:
| Step | Label | What you do |
|---|---|---|
| 1 | Choose OS | Pick Linux, FreeBSD, or Windows |
| 2 | Host details | Friendly name, host groups, optional integrations |
| 3 | Copy command | Copy the generated install one-liner |
| 4 | Connection | Wait for the agent to check in |
Step 2: Choose the Operating System
Pick the OS of the target host. This determines which installer the server serves and which binary is downloaded.
| OS | Installer | Binary format |
|---|---|---|
| Linux | POSIX shell script (patchmon_install.sh) |
patchmon-agent-linux-<arch> |
| FreeBSD | POSIX shell script (same as Linux, with os=freebsd query parameter) |
patchmon-agent-freebsd-<arch> |
| Windows | PowerShell script (patchmon_install_windows.ps1) |
patchmon-agent-windows-<arch>.exe |
The architecture is auto-detected at install time: you do not pick it in the UI. The installer maps uname -m (or PROCESSOR_ARCHITECTURE on Windows) onto one of amd64, arm64, arm, or 386 and downloads the matching binary.
Windows 32-bit (x86) is not supported. The installer aborts with a clear error. All Microsoft-supported Windows versions as of 2026 are 64-bit only.
Step 3: Host Details
Fill in the form:
| Field | Required | Notes |
|---|---|---|
| Friendly Name | Yes | Free-form label shown in the UI (e.g. web-01.prod). Does not have to match the real hostname; the real hostname is learned from the agent's first report. |
| Host Groups | No | Tick one or more groups to pre-tag the host. Groups can be changed later. |
| Docker integration | No | Enables Docker container/volume/image reporting. Can be toggled later from the host detail page. |
| Compliance integration | No | Enables the OpenSCAP compliance scanner. Can be toggled later. |
Click Next. The UI calls POST /api/v1/hosts to create the host record and receives a plaintext api_id and api_key back. These are only rendered into the copy-paste command on the next step. They are never stored or shown again in the UI. If you lose the command before running it, regenerate credentials from the host detail page (see Managing the PatchMon Agent).
Step 4: Copy the Install Command
The wizard now shows a one-liner tailored to the OS you picked. Examples:
Linux one-liner:
curl -s "https://patchmon.example.com/api/v1/hosts/install" \
-H "X-API-ID: patchmon_a1b2c3d4" \
-H "X-API-KEY: <64-char-key>" | sudo sh
FreeBSD one-liner (note: no sudo; FreeBSD installs run as root directly; use su - first if you are not root):
curl -s "https://patchmon.example.com/api/v1/hosts/install?os=freebsd" \
-H "X-API-ID: patchmon_a1b2c3d4" \
-H "X-API-KEY: <64-char-key>" | sh
Windows one-liner (elevated PowerShell, single line):
Invoke-WebRequest -Uri "https://patchmon.example.com/api/v1/hosts/install?os=windows" -Headers @{"X-API-ID"="patchmon_a1b2c3d4"; "X-API-KEY"="<64-char-key>"} -UseBasicParsing -OutFile "$env:TEMP\patchmon-install.ps1"; & "$env:TEMP\patchmon-install.ps1"
Click Copy command. The wizard advances to Step 5: Connection automatically.
Windows Options
On the Windows step, two optional tick-boxes are shown:
| Option | When to use |
|---|---|
| Self-signed certificate (SSL bypass) | Your PatchMon server uses a private/internal CA that Windows does not trust. The emitted command sets [Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } before calling the server. |
| Use curl instead of Invoke-WebRequest | Some hardened / older Windows hosts fail with Invoke-WebRequest due to TLS 1.0/1.1 negotiation or chunked-transfer issues. Ticking this box uses curl.exe (shipped with Windows 10 1803+ and Server 2019+) instead. |
If your server is using a commercially signed TLS certificate (Let's Encrypt, commercial CA), leave both unticked.
The --force Flag (Linux only)
The Linux installer supports a --force flag that bypasses broken apt packages during dependency installation. The wizard does not surface this by default. If apt-get update or apt-get install curl fails on the target host, re-run the install command with --force appended after sh:
curl -s "https://patchmon.example.com/api/v1/hosts/install?force=true" \
-H "X-API-ID: ..." -H "X-API-KEY: ..." | sudo sh -s -- --force
Step 5: Run the Command on the Target Host
Paste the command into a terminal on the target host.
Linux / FreeBSD: Run as root or with sudo. The installer refuses to run otherwise.
Windows: Right-click PowerShell → Run as Administrator, then paste.
The installer will:
- Verify the system date/time (interactive confirmation when run on a TTY; silent continue when piped).
- Detect the package manager (
apt,dnf,yum,zypper,pacman,apk, orpkg) and installcurlif missing. - Exchange the bootstrap token for the real
api_id/api_keyviaPOST /api/v1/hosts/bootstrap/exchange. - Create
/etc/patchmon/(Linux/FreeBSD) orC:\ProgramData\PatchMon\(Windows) withconfig.ymlandcredentials.yml, both with0600/ Administrator-only permissions. - Download the matching agent binary from
GET /api/v1/hosts/agent/download?arch=<arch>&os=<os>. - Run
patchmon-agent pingto confirm credentials work. - Register the service:
- systemd on most Linux distros:
/etc/systemd/system/patchmon-agent.service - OpenRC on Alpine:
/etc/init.d/patchmon-agent - rc.d on FreeBSD:
/usr/local/etc/rc.d/patchmon_agent - Crontab fallback if no init system is detected
- Windows Service Control Manager on Windows: service name
PatchMonAgent, startup type Automatic
- systemd on most Linux distros:
- Start the service, which opens the WebSocket and sends the initial system report.
On a clean host with a working network, the whole process takes 10–30 seconds.
openSUSE and SLES: the installer detects
zypperand completes successfully, but package inventory on SUSE-family hosts is coming soon. The agent has no zypper backend yet, so step 8 fails withunsupported package manager: unknownand the host stays on "Waiting for initial system report" rather than becoming active. A successful install on these systems does not mean the host is being monitored. Follow and vote for zypper support at feedback.patchmon.net.
Step 6: Watch the "Waiting for Connection" Screen
The wizard now shows the connection progress. It polls /api/v1/ws/status/{apiId} every 2 seconds and transitions through four states:
| State | Meaning |
|---|---|
| Waiting for connection | No WebSocket has been opened yet. The installer is still running on the host, or the host cannot reach the server. |
| Connected | The agent has opened a WebSocket but has not yet sent a report. The initial report runs in the background right after serve starts. |
| Receiving initial report | The agent sent a report and the server is processing it (OS type, hostname, IP, architecture, packages). |
| Done | Enrolment is complete. After a brief "Done" state the wizard redirects you to Hosts → |
If you close the wizard before reaching Done, the enrolment still completes in the background. The host appears in the Hosts list with status "Pending" until the first report lands, then flips to "Active".
What Gets Installed
| Path (Linux / FreeBSD) | Path (Windows) | Purpose |
|---|---|---|
/usr/local/bin/patchmon-agent |
C:\Program Files\PatchMon\patchmon-agent.exe |
Agent binary |
/etc/patchmon/config.yml |
C:\ProgramData\PatchMon\config.yml |
Agent configuration |
/etc/patchmon/credentials.yml |
C:\ProgramData\PatchMon\credentials.yml |
API credentials (api_id, api_key) |
/etc/patchmon/logs/patchmon-agent.log |
C:\ProgramData\PatchMon\patchmon-agent.log |
Rolling log file |
/etc/systemd/system/patchmon-agent.service (systemd) |
systemd unit | |
/etc/init.d/patchmon-agent (OpenRC) |
OpenRC init script | |
/usr/local/etc/rc.d/patchmon_agent (FreeBSD) |
FreeBSD rc.d script | |
Windows Service PatchMonAgent |
Service Control Manager entry |
For the full reference of every config.yml parameter, see Agent Configuration Reference (config.yml).
Troubleshooting First Check-in
If the "Waiting for connection" screen never moves past its initial state, work through the checks below in order.
1. Host cannot reach the server
From the target host:
curl -v https://patchmon.example.com/health
# expected: HTTP/1.1 200 and body "healthy"
If this fails:
- DNS:
nslookup patchmon.example.comordig patchmon.example.com. Fix/etc/resolv.confor update your internal DNS. - Routing / firewall:
traceroute patchmon.example.comand check outbound TCP/443 is allowed. Corporate firewalls frequently block egress to new hostnames. - Proxy: if the host is behind an outbound HTTP proxy, set
HTTPS_PROXYandHTTP_PROXYin the environment before pasting the install command.
2. Certificate validation fails
Symptoms: curl: (60) SSL certificate problem on Linux, or Could not establish trust relationship for the SSL/TLS secure channel on Windows.
- Preferred fix: install your CA into the host's system trust store:
- Debian/Ubuntu: copy the CA into
/usr/local/share/ca-certificates/and runupdate-ca-certificates. - RHEL/Rocky/Fedora: copy the CA into
/etc/pki/ca-trust/source/anchors/and runupdate-ca-trust. - Alpine:
apk add ca-certificates, then copy andupdate-ca-certificates. - Windows: import the CA into Local Computer → Trusted Root Certification Authorities via
certlm.msc.
- Debian/Ubuntu: copy the CA into
- Quick bypass (lab only): in the PatchMon web UI, go to Settings → Server → Ignore SSL self-signed and toggle it on. The server will then serve install scripts with
curl -skand injectskip_ssl_verify: trueinto the generatedconfig.yml. On Windows, tick Self-signed certificate (SSL bypass) on Step 3 of the wizard before copying the command.
Do not use
skip_ssl_verifyin production. It disables TLS verification entirely and exposes the agent to man-in-the-middle attacks. See the Agent Configuration Reference for more onskip_ssl_verify.
3. "CORS error" in the browser (wizard-side only)
The wizard itself calls /api/v1/hosts/install and /api/v1/ws/status/{apiId} from your browser. If either fails with a CORS error, your server's CORS_ORIGIN env var does not match the URL you are accessing PatchMon with.
Fix: set CORS_ORIGIN in the server .env to the exact origin (protocol + host + port) the browser uses. For example:
CORS_ORIGIN=https://patchmon.example.com
If users reach PatchMon from more than one URL (e.g. an external domain and an internal LAN address), comma-separate the values with no spaces:
CORS_ORIGIN=https://patchmon.example.com,https://patchmon.internal.lan
Then restart the server container (docker compose restart server). See Server Troubleshooting for the full CORS section.
4. Outbound port 443 blocked
Many cloud and enterprise networks allow port 80 but block 443 to arbitrary hosts. Test with:
# TCP reachability
nc -vz patchmon.example.com 443
# or
timeout 5 bash -c "</dev/tcp/patchmon.example.com/443" && echo "open" || echo "blocked"
If blocked, allowlist outbound TCP/443 to your PatchMon server on the egress firewall.
5. Bootstrap token expired
The bootstrap token in the install command is valid for 5 minutes and is single-use. If you copy the command, wait too long, and then run it, you will see:
ERROR: Failed to fetch credentials. Bootstrap token may have expired.
Please request a new installation script.
Go back to Step 4 of the wizard and click Copy command again. This reuses the same host record but mints a fresh token.
6. Installer aborts on clock skew
If the host's clock is more than a few minutes off UTC, TLS handshakes will fail. On Linux:
sudo timedatectl set-ntp true
sudo timedatectl set-timezone Europe/London
On Windows (elevated PowerShell):
w32tm /resync
Set-TimeZone -Name "GMT Standard Time"
Then re-run the install command.
7. Agent installed but stays "Pending"
The binary installed and the service started, but the host never flips to "Active" in the UI. Run on the target host:
sudo patchmon-agent diagnostics
sudo patchmon-agent ping
sudo systemctl status patchmon-agent # or: rc-service patchmon-agent status
sudo journalctl -u patchmon-agent -n 50 # or: tail -n 50 /etc/patchmon/logs/patchmon-agent.log
For the detailed checks, see Managing the PatchMon Agent: Common Troubleshooting and Agent Troubleshooting.
8. Reverse proxy drops the WebSocket
If the "Connected" state never appears but HTTP requests work fine, your reverse proxy is not forwarding the Upgrade: websocket handshake. The agent opens its WebSocket against /api/v1/agents/ws. See Server Troubleshooting: Agent cannot connect over WebSocket for Nginx / Traefik / Caddy config snippets.
Re-running the Installer on an Already-Enrolled Host
The Linux installer is idempotent. If you paste the install command on a host that is already enrolled and healthy, it will:
- Detect existing
config.yml,credentials.yml, and binary. - Run
patchmon-agent ping. - Exit early with
Agent is already configured and ping successfuland leave everything untouched.
To force a full reinstall:
sudo rm -f /etc/patchmon/config.yml /etc/patchmon/credentials.yml
# then paste the install command again
Or regenerate credentials from the UI first (this rotates the API key, and the next installer run will pick up the new one).
Next Steps
- Managing the PatchMon Agent: CLI commands, service control, logs, updates, removal.
- Agent Configuration Reference (config.yml): every config parameter, with defaults.
- Proxmox LXC Auto-Enrollment Guide: bulk-enrol containers via the auto-enrolment token API.
- Uninstalling the PatchMon Agent: remove the agent from a host.
- Agent Troubleshooting: quick decision tree for common symptoms.