PatchMon on Ubuntu 24.04 on Azure User Guide
Overview
This image runs PatchMon 2.0.2, a self hosted Linux patch management platform, on Ubuntu 24.04 LTS. PatchMon is a central server: you register the Linux hosts you want to manage, and it keeps an inventory of every package on each of them, tells you which have outstanding security and regular updates, groups your hosts, schedules and runs patching jobs, and reports on the patch posture of the whole fleet. It also collects Windows update metadata, runs SCAP based compliance scanning, and provides an in browser remote console.
cloudimg delivers the PatchMon server fully installed and managed by systemd, with PostgreSQL 16 and Redis 7 already configured, so a working patch management console is available within minutes of launch.
What is included:
- PatchMon 2.0.2, installed from the official upstream release binary and verified by SHA256 checksum at install time
- A single statically linked server that serves both the web console and the REST API on port
3000, with the frontend and all database migrations embedded in the binary - PostgreSQL 16 as the primary database and Redis 7 as the job queue and websocket coordination backend
- The official agent binaries for
linux/amd64,linux/arm64,linux/386andlinux/arm, staged locally so the server can hand each host you add the right one - SCAP Security Guide 0.1.81 datastreams staged for the compliance scanning module
- guacd for the in browser remote console, started and supervised by PatchMon itself and bound to loopback only
- Secure by default: no administrator account and no shared secret ship in the image. On first boot a unique database password, a unique Redis password, fresh signing and encryption keys and a single unique administrator account are generated and written to a root only file
- A fully patched Ubuntu 24.04 LTS base with unattended security upgrades enabled
- 24/7 cloudimg support
This image is the PatchMon server. The PatchMon agent is a separate component that you install on each host you want managed; PatchMon generates the exact one line install command for you in the console, and Step 7 walks through it. No agent, enrolment token or host identity is baked into this image.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is the recommended size for evaluation and for fleets up to roughly fifty hosts; for larger fleets choose a size with more memory and raise DB_CONNECTION_LIMIT. NSG inbound: allow 22/tcp from your management network and 3000/tcp for the web console. Your managed hosts need outbound access to port 3000 on this VM. PatchMon serves plain HTTP on port 3000; for production or shared access, put a TLS terminating reverse proxy in front of it (see the final step).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for PatchMon by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key. Then Review + create then Create. Once the VM exists, add an inbound NSG rule allowing 3000/tcp from the networks you will browse from.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name patchmon \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
# Open the console port so you can reach the web interface
az vm open-port --resource-group <your-rg> --name patchmon --port 3000 --priority 900
Step 3 - Retrieve the per VM administrator credentials
Nothing secret is baked into the image. PatchMon ships with no user accounts at all. On the very first boot a one shot service (patchmon-firstboot.service) generates a fresh database password, a fresh Redis password, fresh JWT, session and encryption keys, and a single administrator account, all unique to your instance. It writes them to /root/patchmon-credentials.txt, readable only by root. SSH in as azureuser and read them:
$ sudo cat /root/patchmon-credentials.txt
# PatchMon - generated on first boot by patchmon-firstboot.service.
# These credentials are unique to THIS VM. Store them somewhere safe.
patchmon.url=http://20.55.10.42:3000
patchmon.admin.user=admin
patchmon.admin.email=admin@example.com
patchmon.admin.pass=Xq7pR2mK9wU4nZ8vB3hL6yD1e5
# Canonical keys consumed by the cloudimg smoke verifier + guide substitution:
PATCHMON_URL=http://20.55.10.42:3000
PATCHMON_ADMIN_USER=admin
PATCHMON_ADMIN_PASSWORD=Xq7pR2mK9wU4nZ8vB3hL6yD1e5
The values above are an example; your instance has its own unique credentials. Confirm the file is root only and that the administrator username is present:
sudo stat -c '%a %U:%G %n' /root/patchmon-credentials.txt
sudo grep -c '^PATCHMON_ADMIN_PASSWORD=' /root/patchmon-credentials.txt
The file reports 600 root:root and the grep reports 1.
Step 4 - Confirm the services are running
Check that the PatchMon server, PostgreSQL and Redis are all active:
sudo systemctl is-active patchmon postgresql redis-server
All three report active. The health endpoint is a real dependency probe: it only reports healthy when both the database and Redis are reachable.
curl -s 'http://localhost:3000/health?format=json'
PatchMon publishes the console on port 3000; PostgreSQL, Redis and the guacd remote console daemon all bind to loopback only, so they are never reachable from outside the VM:
sudo ss -tln | grep -E ':(3000|4822|5432|6379) '

Step 5 - Confirm the secure defaults
The first boot service has run to completion and the application service is running on the secrets it generated. The environment file holding every per VM secret is readable only by root and the patchmon service account:
sudo systemctl is-active patchmon-firstboot patchmon
sudo stat -c '%a %U:%G %n' /etc/patchmon-server/patchmon.env
Both report active, and the environment file reports 640 root:patchmon.
The application service is deliberately gated on a bootstrap marker that only the first boot service creates, so on a fresh instance PatchMon cannot start before its secrets have been generated. There is no window in which the vendor defaults are live.
Once it has finished, the first boot service disables itself and is guarded by a sentinel file, so it never runs again and never regenerates your credentials on a later reboot. That is why systemctl is-enabled patchmon-firstboot reports disabled on a running instance while is-active reports active — the unit has already done its job.

Step 6 - Sign in to the web console
Browse to http://<vm-ip>:3000 and sign in as admin with the password from /root/patchmon-credentials.txt.

After signing in you land on the dashboard, which summarises your fleet: how many hosts you manage, how many are up to date, how many need updating, how many outstanding packages and security updates there are across all of them, and how those numbers are trending.

The dashboard on your own instance starts empty. It fills in as soon as you register your first host, which is the next step.
Step 7 - Register the Linux hosts you want to manage
PatchMon manages a host by way of a small agent that runs on it and reports its package inventory back to this server. You never have to assemble that command yourself.
In the console choose Hosts, then Add Host. Give the host a friendly name and save it. PatchMon creates the host record, issues it a unique API identity, and shows you a one line install command containing a short lived bootstrap token. Run that command as root on the host you want to manage (not on this server) and the agent installs itself, exchanges the bootstrap token for its own permanent credentials, and reports in.
Each host you add gets its own credentials, so revoking one host never affects the others. Once a host has reported, it appears in the Hosts list with its real operating system, agent version, connection state and update status:

The agent binaries PatchMon hands out are staged locally on this image, so host enrolment does not depend on outbound internet access from the server:
ls -1 /opt/patchmon/agents/
Step 8 - Review what needs patching
Packages is the fleet wide package inventory. It lists every package PatchMon has seen across all your hosts, how many hosts each is installed on, whether an update is available, whether that update is a security update, and the version it would move to. This is the view that answers "what is outstanding across my estate, and how much of it is security relevant".

From Patching you can then schedule or immediately run a patching job against a single host or a group of hosts, and review the outcome of every run.
Step 9 - Query the same data from the REST API
Everything in the console is available over the REST API on the same port. Authenticate with the administrator credentials to get a bearer token, then call any endpoint with it. This reads the real password from the root only credentials file, so nothing secret has to be typed or pasted:
PW=$(sudo sed -n 's/^PATCHMON_ADMIN_PASSWORD=//p' /root/patchmon-credentials.txt)
TOKEN=$(curl -sS -X POST http://localhost:3000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d "{\"username\":\"admin\",\"password\":\"$PW\"}" \
| sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
test -n "$TOKEN" && echo "authenticated: session token issued"
With the token you can ask PatchMon for the fleet's patch posture:
PW=$(sudo sed -n 's/^PATCHMON_ADMIN_PASSWORD=//p' /root/patchmon-credentials.txt)
TOKEN=$(curl -sS -X POST http://localhost:3000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d "{\"username\":\"admin\",\"password\":\"$PW\"}" \
| sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -sS -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/v1/dashboard/stats \
| sed -n 's/.*"cards":{\([^}]*\)}.*/\1/p'

The API is closed by default. An anonymous request is rejected, and a wrong password issues no session token at all:
curl -sS -o /dev/null -w 'anonymous GET /api/v1/hosts -> HTTP %{http_code}\n' \
http://localhost:3000/api/v1/hosts
BAD=$(curl -sS -X POST http://localhost:3000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"wrong-password-1A!"}' \
| sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
test -z "$BAD" && echo "wrong password: no session token issued"

Step 10 - Where everything lives
| Path | Contents |
|---|---|
/opt/patchmon/patchmon-server |
the PatchMon server binary |
/opt/patchmon/agents/ |
the official agent binaries served to your hosts |
/opt/patchmon/ssg-content/ |
SCAP Security Guide datastreams for compliance scanning |
/etc/patchmon-server/patchmon.env |
every per VM secret and setting (0640 root:patchmon) |
/root/patchmon-credentials.txt |
the administrator credentials generated on first boot (0600 root:root) |
Useful operations:
sudo systemctl status patchmon --no-pager | head -5
sudo journalctl -u patchmon --since '10 minutes ago' --no-pager | tail -5
The database is standard PostgreSQL, so back it up the way you back up any PostgreSQL database:
sudo -u postgres pg_dump patchmon_db | gzip > /tmp/patchmon-backup.sql.gz && ls -lh /tmp/patchmon-backup.sql.gz
Step 11 - Put PatchMon behind TLS on your own domain
For production, terminate TLS in front of PatchMon rather than exposing port 3000 directly. Install your preferred reverse proxy (nginx, Caddy or Traefik) on this VM or in front of it, obtain a certificate for your domain, and proxy it to 127.0.0.1:3000, forwarding Upgrade and Connection headers so the agent websockets and the remote console keep working.
Two settings must then be updated so PatchMon agrees with its new address, both in /etc/patchmon-server/patchmon.env:
CORS_ORIGIN- PatchMon only accepts requests whose address is listed here. Add yourhttps://patchmon.example.comorigin, or the console will refuse to load with a host mismatch error.TRUST_PROXY=true- so PatchMon reads the real client address and scheme from the proxy's forwarded headers.
Then set the same URL as the server URL under Settings, so the agent install commands PatchMon generates point at your domain rather than the raw IP, and restart the service:
sudo systemctl restart patchmon && sleep 5 && curl -s http://localhost:3000/health
Existing agents pick up the new address on their next check in.
Support
This image is published by cloudimg with 24/7 support for deployment, upgrades, TLS termination, reverse proxy and SSO integration, agent rollout across your fleet, and scaling. Raise a request through the Azure Marketplace listing or contact cloudimg support directly.
PatchMon is open source software released under the GNU Affero General Public License v3.0. PatchMon is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.