Chapter 8 of 26
Patching Overview
What Patching Is
Patching is the PatchMon feature that lets you deploy package and security updates to your Linux and FreeBSD hosts on demand or on a schedule, with validation, approval, stop, retry, and live log streaming over WebSocket. You drive it from the Patching page in the web UI, or from the Patching tab on any Host Detail page.
Patching in 2.0 is a first-class module rather than a side-feature. Runs are persisted, queued through Redis/asynq, executed by the agent on the host, and streamed back to the browser live.
Module Gate
Both halves of the feature are gated by a capability module. If the module is not enabled on your plan, the relevant UI appears locked with an "Upgrade required" placeholder and the corresponding API routes reject the request.
| UI area | Required module |
|---|---|
| Patching dashboard, Runs & History, trigger a patch run, approve, stop, retry | patching |
| Policies tab, policy assignments, exclusions, scheduled runs | patching_policies |
Fleet-wide the Patching page is hidden from the sidebar when patching is not enabled. The Policies tab inside the page is shown with a tier badge when patching is enabled but patching_policies is not.
Who Can Use It
Patching uses two RBAC permissions on top of the module gate:
| Action | Permission | Route pattern |
|---|---|---|
| View dashboard, list runs, open a run, watch the live stream | can_view_hosts |
GET /patching/* |
| Trigger a run, approve, retry validation, stop, delete a run | can_manage_patching |
POST /patching/trigger, POST /patching/runs/{id}/approve, etc. |
| View policies | can_view_hosts |
GET /patching/policies |
| Create, edit, delete policies and policy assignments / exclusions | can_manage_patching |
POST/PUT/DELETE /patching/policies/* |
If your role has can_view_hosts but not can_manage_patching, you will see the Patching page read-only. The action buttons (Patch all, Approve, Stop, Delete) either do not appear or return a 403.
The Three Core Concepts
Everything in the Patching module revolves around three concepts:
1. Patch Run
A patch run is one unit of patching work against a single host. Every time you click "Patch all" on a host, approve a submission, or retry a validation, a run row is created in the database.
Each run has:
- A type:
patch_all(install all available package updates) orpatch_package(install a specific package or set of packages). - A dry-run flag: if
dry_run=true, the agent reports what would change without actually installing anything. On Linux and FreeBSD this only applies topatch_package;patch_allcannot be dry-run there because the agent's bulk upgrade path does not support--dry-runfaithfully. Windows hosts support a dry run for both types. - A status that moves through the run lifecycle (see below).
- A persisted shell output that captures every line of stdout/stderr produced by the package manager. Live subscribers get it streamed over WebSocket; everyone else gets the full blob on completion.
- Optional policy metadata: the effective patch policy is snapshotted onto the run at trigger time, so you can see in run detail "which policy was in effect when this was queued".
Run statuses
The server moves a run through these statuses, visible as badges in the Runs & History table and in the run detail header:
| Status | Meaning |
|---|---|
queued |
The execution task has been enqueued on the asynq queue, waiting for the worker to pick it up. |
pending_validation |
A dry-run was queued for validation but has not completed yet (the host may be offline). |
validated |
The dry-run finished successfully; the run is waiting for an operator to approve it. |
pending_approval |
A patch run was submitted for approval without a dry-run (e.g. a patch_all that cannot be dry-run). An approver needs to sign off before the run is queued. |
approved |
The original validation run after someone approved it. A new execution run (linked by validation_run_id) is created alongside this row and queued. |
scheduled |
The run has been accepted but is waiting for its run_at timestamp (delayed or fixed-time policy). Scheduled runs do not block other runs on the host; when the window arrives the run moves to queued, or is cancelled if another run is still active on the host. |
running |
The agent is currently executing the package manager command on the host. This is the status that opens the live WebSocket stream. |
completed |
The run finished successfully. The persisted shell_output is now authoritative. |
dry_run_completed |
A dry-run finished successfully (terminal state for dry-runs that aren't turned into a real run). |
failed |
The run finished with a non-zero exit status or the host reported an error. |
cancelled |
The run was stopped by an operator clicking Stop Run (or deleted before execution), or a scheduled run was skipped because another run was active on the host when its window arrived. The cancel is applied authoritatively in the database first; if the agent is connected the server also sends a courtesy patch_run_stop so the running subprocess is interrupted. With this ordering, an offline or unresponsive agent can't leave the row stuck in running. |
timed_out |
The periodic patch-run cleanup found this run still in running state past the configured stall timeout (PATCH_RUN_STALL_TIMEOUT_MIN, default 30 minutes) and marked it as timed out, or found a scheduled, queued or pending_validation run that was not dispatched within 24 hours of becoming due. The cleanup sweep runs every 10 minutes. |
agent_disconnected |
The agent's WebSocket dropped while this run was running. The server marks every in-flight run for that host as agent_disconnected so the row doesn't sit at running indefinitely. If the agent reconnects and posts a late completed / failed / cancelled for the same run, the server will update the row to that final state. |
2. Patch Policy
A patch policy controls when an approved patch run actually fires. Policies are optional; a host with no policy attached gets the implicit "Default" policy, which runs patches immediately on trigger.
Each policy has a delay type:
- Immediate: run as soon as the task is dequeued.
- Delayed: wait N minutes after the trigger before running (useful for "give me 30 minutes to change my mind").
- Fixed time: run at a specific wall-clock time (
HH:MM) interpreted as local time in the organization timezone (Settings → General → Timezone). Used for maintenance windows.
Policies are assigned to hosts or host groups. You can also add per-host exclusions to carve specific hosts out of a group-assigned policy. See Patch Policies and Scheduling for full details.
3. Dry-Run / Validation
A dry-run (also called a validation run) asks the agent to simulate the package installation without applying it. It exists to catch problems before you touch the host:
- For
apt-get, the agent runsapt-get -s install <packages>and parses the simulation output. - For
dnf/yum, the agent runsdnf upgrade --assumeno <packages>(oryum), which prints the transaction plan and stops before committing. Every package in the plan, including dependencies, is listed in the validation result. - For
pkgon FreeBSD, the agent runspkg upgrade -nor the equivalent install-no-run. pacman(Arch Linux) has no validation step, because selected patching is not available on Arch andpatch_allcannot be dry-run. See Arch Linux patching.- For
apkon Alpine, the agent runsapk upgrade --simulate <packages>, which opens the package database read-only. A validation fails if apk reports an error (for example an unknown package or a conflict with/etc/apk/world), or if the plan would downgrade or replace a package, or remove one listed in/etc/apk/world.
When the dry-run completes the run transitions to validated and shows you the list of packages that would be installed, including dependencies that were pulled in. If more packages would be installed than you originally asked for, the UI badges the run with Extra deps (Linux and FreeBSD patch_package runs only) and surfaces the full list in the run detail "Packages affected" panel so you can review before approving.
Approval is the step that turns a validated dry-run into a real patching run. On approval:
- The validation run is marked
approved(terminal) and preserved with its output for audit. - A new patch run is created with
dry_run=false, linked to the validation viavalidation_run_id. - The new run is enqueued against the effective policy (so "Approve & Patch" at 14:00 on a host with a
03:00fixed-time policy produces ascheduledrun, not an immediate one). - You can override the policy at approval time by picking Immediate in the approve wizard, which bypasses the delay.
Multi-OS Coverage
The agent chooses the patching back-end by detecting the host's package manager. Linux, FreeBSD and Windows patching are supported; Windows patching has its own path.
| Package manager | OSes | Supported for patching |
|---|---|---|
apt-get |
Debian, Ubuntu, Raspbian | Yes |
dnf |
RHEL 8+, Rocky, AlmaLinux, Fedora | Yes |
yum |
RHEL 7, CentOS 7 | Yes |
pacman |
Arch Linux, Manjaro | Patch all only. See Arch Linux patching. |
pkg |
FreeBSD 13+ (plus freebsd-update for the base system on patch_all) |
Yes |
apk |
Alpine Linux 3.21 and later (apk-tools 2 on 3.21 and 3.22, apk-tools 3 from 3.23) | Yes, with agent 2.1.4 or later. See Alpine patching. |
zypper |
openSUSE Leap 15.6 and 16.0, openSUSE Tumbleweed and Slowroll, SLES 15 and 16 | Yes, with agent 2.1.4 or later. See openSUSE and SLES patching. |
apt-get and univention-upgrade |
Univention Corporate Server 5.0 and 5.2 (tested on 5.0-10 and 5.2-7) | Yes, with agent 2.1.4 or later. See Univention Corporate Server patching. |
| Windows Update Agent (WUA) + winget | Windows 10/11, Server 2019/2022/2025 | Yes, with agent 2.1.4 or later (separate path) |
Arch Linux patching
Arch Linux does not support partial upgrades: syncing the package databases and then upgrading only some packages can leave a package linked against libraries that are not installed yet. The Arch Wiki explains this under System maintenance: Partial upgrades are unsupported. Upgrading curl this way on an out-of-date host can stop pacman itself from starting, and then the host cannot be patched at all.
PatchMon therefore offers only Patch all on hosts that use pacman. It runs pacman -Syu --noconfirm, which syncs the databases and upgrades every package in one transaction.
Patch selected is refused at every step, all with the same message: "Arch Linux does not support partial upgrades, so PatchMon cannot patch selected packages on this host. Use Patch all."
- The UI disables it for Arch hosts and explains why.
- The server rejects a new
patch_packagerun for an Arch host. - A
patch_packagerun for an Arch host that was queued or scheduled before you upgraded PatchMon is marked failed with the same message when it comes due. It is never sent to the agent. - The agent refuses one if it still receives it, before running any
pacmancommand. It also refuses a dry run on Arch, becausepacman -Syu -psyncs the package databases without upgrading.
Older pending runs. An Arch patch_package run left waiting for validation or approval from before the upgrade can no longer be approved or retried. Delete it from Patching → Runs & History and use Patch all instead.
Alpine patching
Alpine hosts are patched with apk on Alpine 3.21 and later, with agent 2.1.4 or later: apk-tools 2 on 3.21 and 3.22, apk-tools 3 from 3.23. Every run, real or dry, plans before it changes anything:
-
apk updaterefreshes the package indexes. -
apk upgrade --simulateworks out the transaction. On a real run, if apk says the plan is unreliable because apk-tools itself has an update, the agent installs that update first (--self-upgrade-onlyon apk-tools 2,--preupgrade-onlyon apk-tools 3), says so in the run output, and plans again. If it cannot tell which apk-tools version is installed, it stops instead. -
The agent refuses the run, before changing anything, if the plan would downgrade or replace a package, remove a package listed in
/etc/apk/world, or contains a step it does not recognise. The error names each step and its likely cause. Review the plan withapk upgrade --simulateon the host.apk also removes packages that nothing needs any more, for example a library a newer package no longer depends on. That is normal clean-up, so the agent lets it happen when the package is not in
/etc/apk/world, and names each one in the run output:Removing gdbm 1.26-r0: no longer required by any package and not in /etc/apk/world.A dry run saysWould removeinstead. Removed packages are listed under the run's affected packages. -
A real run then applies the plan with
apk upgrade.
Patch selected runs apk upgrade <packages>, never apk add, so /etc/apk/world is not changed. Packages built from the same source as a selected package are upgraded with it: selecting libssl3 also upgrades libcrypto3, because apk will not upgrade one without the other. If apk still leaves a selected package alone, the run output says so; select the packages it needs as well, or use Patch all.
Held packages. A version constraint in /etc/apk/world (for example busybox=1.37.0-r29) holds a package at that version. apk skips held packages, and so does PatchMon: the run succeeds and its output names each one, for example Held by /etc/apk/world: busybox=1.37.0-r29 (1.37.0-r30 available), along with any package held back with it. Held packages are reported as up to date in the package inventory rather than offered for patching.
If the installed version of a package breaks its own constraint in /etc/apk/world, apk refuses every upgrade. PatchMon then fails the run before changing anything and names the line to correct, for example change busybox<1.0 to busybox=1.37.0-r29 or remove the version, then retry.
Security updates on Alpine come from the Alpine security database (secdb.alpinelinux.org). An update is marked as a security update when it moves the package past a version that fixed a published issue. The agent fetches the database for the branch in /etc/apk/repositories at most every 6 hours. If it cannot reach it, it tries again after an hour, and meanwhile the report still goes through, using the last copy it fetched or, without one, marking no updates as security updates. See apk_secdb in the Operator Guide to turn the lookup off.
Reboot required is set when the installed kernel package (linux-lts, linux-virt and so on) is newer than the running kernel. Alpine containers (Docker or LXC) run the host's kernel, so they never report a reboot as required.
Univention Corporate Server patching
Univention Corporate Server (UCS) hosts use the Linux agent: choose Linux in the Add Host wizard. With PatchMon 2.1.4 or later, server and agent, they are listed as Univention Corporate Server with their release, patch level and errata level, for example 5.2-7 errata606. Older agents show UCS 5.0 as Debian 10 and UCS 5.2 without its patch level, but report and patch through apt as they do on Debian.
Inventory comes from apt, as on Debian, so the outdated packages are the errata updates published for the installed release.
| Run | Command |
|---|---|
| Patch all | univention-upgrade --noninteractive --disable-app-updates --updateto <installed release>, for example --updateto 5.2-7 |
| Patch selected | apt-get --only-upgrade install <packages>, as on Debian |
Patch all installs every errata update for the installed release through Univention's own updater, which also clears the "updates available" notice in the Univention Management Console. It never starts a release update, such as 5.2-7 to 5.2-8 or 5.0-10 to 5.1-0, and never updates App Center apps. Neither appears in the package list PatchMon shows, and a release update can need preparation and a maintenance window. Install them from the Univention Management Console, or by running univention-upgrade on the host. When a release update is waiting, the run output says so: 5.1-0 is available but updater has been instructed to stop at version 5.0-10.
Security updates. Univention publishes fixes as errata updates, not through a separate security repository, so PatchMon does not mark UCS updates as security updates.
Compliance scanning. UCS 5.2 is scanned with the Debian 12 OpenSCAP content and CIS profiles, which needs agent 2.1.4 or later. UCS 5.0 is based on Debian 10, for which no current OpenSCAP content is published, so compliance scanning is not supported on it.
Reboot required is set when an update installs a newer kernel than the one running.
openSUSE and SLES patching
openSUSE and SLES hosts report inventory and are patched with zypper, with agent 2.1.4 or later. Older agents fail every report on these hosts with unsupported package manager: unknown; update the agent to fix it.
Inventory. Installed packages come from rpm, and available updates from zypper list-updates, which leaves out packages locked with zypper addlock. An update is marked as a security update when a needed patch in the security category updates that package. Tumbleweed and Slowroll publish no patches, and an unregistered SLES host has no update repositories, so neither shows security updates. The repositories list shows each enabled zypper repository, without credentials or the registration token that SUSE Customer Center repositories carry in their URL.
Every run, real or dry, plans before it changes anything:
zypper refreshrefreshes the repositories. If a repository cannot be reached, the run says so and carries on with the metadata zypper already has, so one unreachable repository does not block patching.- The agent asks zypper for the plan with
--dry-runand prints it in the run output. It refuses the run, before changing anything, if the plan would downgrade a package or change its architecture or vendor. Removals are allowed, because an update can obsolete or replace a package, and each one is named in the run output:Removing libcurl-mini4: zypper's plan removes it, usually because an update obsoletes or replaces it.A dry run saysWould removeinstead. - A real run applies the plan.
| Run | openSUSE Leap and SLES | Tumbleweed and Slowroll |
|---|---|---|
| Patch all | zypper patch --with-update: every needed patch, then any update that no patch covers, such as packages from third-party repositories |
zypper dist-upgrade, the only supported way to update a rolling release |
| Patch selected | zypper update <packages> |
zypper update <packages>, with a warning that a partial update of a rolling release is not supported upstream |
Tumbleweed with Packman or other OBS repositories. zypper dist-upgrade keeps each package with the vendor it came from, and PatchMon refuses any plan that changes a package's vendor. If you switch packages to Packman or another vendor, run zypper dup --allow-vendor-change once by hand to make that move deliberately; later runs keep the new vendor and patch normally.
Patch runs include patches that suggest a reboot, such as kernel updates, and accept licence prompts, so an unattended run does not silently skip them.
zypper updating itself. When a patch updates zypper or libzypp, zypper installs that first and asks to be run again. The agent does so, up to three times in one run, and says so in the run output. A dry run cannot see past that step: it shows only zypper's own update and says that a real run will apply more.
Locked packages. Packages locked with zypper addlock are left alone, and the run output names them. Selecting only locked packages fails the run before anything changes; remove the lock with zypper removelock to patch them.
The zypp lock. If another process holds the zypp lock, for example PackageKit or an administrator running zypper, the agent waits 30 seconds and then fails the run, naming the cause. Retry when it has finished.
Exit codes are reported in plain words. A run that needs a reboot (zypper exit code 102) or skipped a repository it could not refresh (106) succeeds with a note in the output. A run where packages were installed but an RPM script failed (107) is marked failed, so that someone checks the output and runs zypper verify on the host.
Dependency problems. If zypper cannot plan an update without a choice, for example removing a package or skipping patches, PatchMon does not choose for you. The run fails and quotes zypper's problem and the solutions it offers. Resolve it once on the host with zypper, then run the patch again.
Leap 16.0 containers made from the stock Proxmox template hit this on their first Patch all. The template ships systemd-sysvcompat, which the Leap 16.0 repository no longer carries, and the systemd update conflicts with it. Run zypper rm systemd-sysvcompat once on the container, then patch.
Transactional hosts. openSUSE MicroOS, Leap Micro and SUSE Linux Micro have a read-only root filesystem and are updated with transactional-update. They report inventory, but patch runs are refused with an explanation, and nothing is changed.
Reboot required is set when libzypp flags it after installing a kernel or another package that needs a reboot (zypper needs-rebooting), or when the running kernel is older than the newest installed one.
Windows patching
Windows hosts are patched from the same places as Linux hosts: Host Detail, the Packages page and the Patch wizard. The agent on the host must be 2.1.4 or later. For an older agent the server refuses the run with "Update the PatchMon agent on this host to v2.1.4 or later to patch Windows"; update the agent first (Host Info → Update Now).
What can be patched
| Item | Installed by | Shown as |
|---|---|---|
| Windows Updates: cumulative, security, .NET, definition, driver and optional updates, and feature upgrades | The Windows Update Agent on the host, from the host's configured update source (Microsoft Update or WSUS) | Source Windows Update |
| Applications that winget can upgrade | winget, by the app's exact winget Id | Source winget |
| Applications found only in the registry | Nothing | Not patchable |
| Microsoft Store apps | Nothing: they update through the Microsoft Store | The available update, with a Microsoft Store app badge; not selectable |
Apps pinned with winget pin |
Nothing until the pin is removed | The available update, with a Pinned in winget badge; not selectable |
The agent reads installed applications from the Windows registry and matches them against winget. PatchMon only ever asks winget to upgrade a package by its exact winget Id. An app that winget has no match for has no Id, so there is no safe way to tell winget which package to upgrade. These apps still report their available version, but they cannot be selected; the tooltip reads "Not patchable: this app was not found in winget. Update it with its own updater." Use the vendor's updater or your software deployment tool for them.
Hosts without winget (it ships with the App Installer package, which Windows Server 2019 and 2022, Server Core and LTSC editions often do not have) list every app as Not patchable. Windows Updates still patch. Windows Server 2025 includes winget as standard.
Microsoft Store apps (apps installed from the Store or as MSIX packages) keep showing their available update, but cannot be patched: "Microsoft Store app: updates through the Microsoft Store". The agent runs as the Local System account, and winget run as Local System only updates the machine's staged copy of a Store app, never the copy each user runs, so an upgrade would report success and change nothing. Let the Microsoft Store update them, or manage them with your Store or Intune policy.
Pinned apps. An app you have pinned on the host with winget pin add also keeps showing its update, with a Pinned in winget badge, and cannot be selected: "Pinned with winget pin; remove the pin on the host to update it". Pinned apps are left out of Patch all. Remove the pin (winget pin remove) and wait for the next report to patch it.
If you select a Store app or a pinned app anyway, for example from an out-of-date page, the wizard refuses the run and lists the app with the same reason.
Windows Update rows also carry badges: Security, Optional, Driver and Feature upgrade.
Patch all
On a Windows host the wizard shows two scope boxes, both ticked by default. At least one must stay ticked:
- Windows Updates: installs every pending Windows update except drivers, optional updates and feature upgrades.
- Applications (winget): upgrades each app that winget reports an upgrade for, one at a time.
Patch all checks the host again when the run starts, so it installs whatever is pending at that moment, not what was pending when the run was queued or validated. Approving a validated Patch all works the same way.
Drivers, optional updates and feature upgrades are never part of Patch all. Select them individually with Patch selected. Selecting a feature upgrade (for example a move to a new Windows 11 version) shows a warning in the wizard: "Feature upgrades download several GB, can take over an hour and need one or more restarts to finish."
To keep something out of Patch all on one host, hide the update in Windows (hidden updates disappear from PatchMon too), or pin the app with winget pin add. Once the next report has seen the pin, the app is shown as pinned and Patch all leaves it out. If the pin was added after the last report, the agent still checks it before upgrading: the app is not upgraded and the item ends Skipped with "Pinned in winget: remove the pin to update it".
Validate first
Windows supports a dry run for both Patch selected and Patch all, so the wizard offers the Validate step for both. A dry run searches Windows Update and asks winget whether an upgrade is available. Nothing is downloaded or installed. Each item ends Would install, with its expected restart behaviour, Not applicable, or Skipped when it could not be installed as things stand: a Microsoft Store app, an app pinned in winget, an update that needs user input, or an update that cannot install until the host has restarted. You can validate up to 100 packages per Windows host in one run.
Approve the result as you would on Linux. When you approve a validated Patch selected run, only the items that would install are carried into the real run.
Per-item results
Run Detail shows an Updates card above the terminal, with one row per update or app: Name, Source, KB, Status, Result (a hint plus the Windows or winget error code) and Restart. It refreshes while the run is active.
| Status | Meaning |
|---|---|
| Installed | Installed by this run. |
| Installed with errors | Windows reported the install as successful with errors. Counted as installed. |
| Installed, restart required | Installed earlier and waiting for the host to restart. PatchMon does not install it again. |
| Already installed | Installed before the run reached it, for example by Windows' own automatic updates. |
| Would install | Dry run only: this would be installed. |
| Not applicable | No longer offered by Windows Update (installed, superseded or withdrawn), no upgrade available, or an app installed for a single user (see below). |
| Skipped | The run was stopped, the run time limit was reached, a restart is needed before more updates can install, the update needs interactive input, an earlier update in the run timed out while installing, the app was pinned with winget pin since the last report, or the app is a Microsoft Store app ("Microsoft Store app: it updates through the Store for each user, not as SYSTEM"). |
| Failed / Aborted | The install did not succeed. The Result column has the code and a hint, for example "Application is in use: close the app and retry". |
One failed item does not stop the others. The run ends Failed if any item failed or was aborted, and its summary lists the first few ("2 of 7 failed: ..."). Skipped items never fail a run. They are listed in the run's summary instead, for example "1 skipped: 7zip.7zip (pinned in winget)", and a run whose only non-successful items were skipped ends Completed with that summary shown as a note.
Restarts
PatchMon never restarts a Windows host. When an update needs a restart:
- Run Detail shows a banner: "A restart is required to finish installing updates." and the item's Restart column is marked.
- The host is flagged as needing a reboot as soon as the run ends, without waiting for the next report, and a
patch_reboot_requirednotification is sent. - The host keeps showing Reboot pending until it has actually restarted, even if Windows' own restart signals clear earlier, with the reason "Restart required to finish a patch run". PatchMon notices the restart from the host's boot time on its next report or ping.
- An app upgrade that finished while the app was open may leave some files to be replaced at the next restart. That item is marked as needing a restart with "Installed; some files will be replaced at the next restart."
- An update that has installed and is waiting for the restart shows as pending restart in the host's package list and is not offered again.
Restart the host when it suits you. If a restart is still pending from an earlier install, Windows may refuse further installs; those items end Skipped with "Restart pending from an earlier install; restart the host and run again."
Stopping a run, and long runs
Stop Run takes effect between items. The update or app being installed at that moment always finishes, because interrupting Windows servicing part-way is worse than waiting; the remaining items end Skipped. Cumulative updates and feature upgrades can run for a long time without output, so the agent writes a progress line every minute and the run is not timed out while it is still working. Each step has its own limit:
| Step | Limit |
|---|---|
| Whole run that installs Windows Updates (a Patch all with Windows Updates ticked, or a Patch selected that includes an update) | 8 hours |
| Whole run that only upgrades apps | 4 hours |
| Searching for Windows Updates | 15 minutes |
| Downloading the run's Windows Updates | 60 minutes, or 180 minutes when a feature upgrade is included |
| Installing one Windows Update | 180 minutes, or 240 minutes for a feature upgrade |
| Upgrading one app with winget | 30 minutes |
A step that runs out of time says so. Windows usually carries on installing an update in the background after its install step times out, so the agent then asks Windows about that update once a minute for up to 10 minutes:
- If Windows finishes it in that time, the item shows Installed with "Installed; Windows finished after the 180-minute limit", or Installed, restart required with "Installed; Windows finished after the 180-minute limit; restart the host to complete it". The run carries on with the next update.
- If Windows still has not finished, the item ends Failed with "Install did not finish within 180 minutes. Windows may still be completing it; check after a restart." PatchMon does not start any more Windows Updates in that run: they end Skipped with "Not started: Windows Update was still installing a previous update". App upgrades still run, and the run ends Failed. Check the host after a restart and run the remaining updates again.
Stopping the run during those 10 minutes ends the check straight away. The limits quoted above are for an ordinary update; for a feature upgrade the install limit is 240 minutes.
Apps installed for a single user
The agent runs as the Local System account, so winget acts on apps installed for all users. An app installed for one user only cannot be upgraded by the agent: it ends Not applicable with "Not found for the system account: the app is probably installed for a single user, which the agent cannot upgrade". Upgrade it from that user's session, or reinstall it for all users.
WSUS-managed hosts
If the host gets updates from WSUS, Windows Update only offers updates that WSUS has approved for it, so PatchMon only ever installs WSUS-approved updates. WSUS stays your approval gate and PatchMon decides when they install. If the host cannot reach its update server, items fail with "Cannot reach the update server or WSUS: check proxy, firewall, WSUS URL". WSUS does not affect winget, which uses its own sources.
Licence terms and app sources
Installing an item accepts its licence terms on your behalf: the Windows Update EULA where one applies, and winget's package and source agreements. winget installers come from the winget community repository and run as Local System on the host. You choose which apps to upgrade.
Agent updates during a patch run
Agent updates are held while a patch run is queued, validating or running on the host, on every operating system. Update Now is refused with "A patch run is in progress on this host. Try again when it finishes.", automatic updates wait, and an agent that is told to update mid-run applies the update after the run and its post-patch report have finished. The reverse also holds: a patch run that reaches the agent while it is downloading or installing its own update fails straight away with "Agent update in progress, retry shortly". Run it again once the agent is back.
The terminal pane on Run Detail shows Windows Update and winget progress instead of apt-get output.
Where Patching Lives in the UI
There are two ways into patching from the left-hand navigation:
- Patching (top-level sidebar item): the fleet-wide view. This is the page described in Running a Patch, Patch Policies and Scheduling, and Patch History and Live Logs.
- Hosts → select a host → Patching tab: the per-host view. Start a Patch all run for that host, watch its packages list, open any previous patch run for this host. The tab is hidden when the
patchingmodule is disabled.
You can also enter the Patching UI from:
- A package link in the Packages page. "Patch this package" starts a
patch_packagewizard pre-loaded with the selected package and the hosts it is installed on. - The Dashboard patching cards (queued/running counts), which deep-link into the Runs & History tab with the appropriate status filter applied.
What Happens When You Click "Patch All"
The end-to-end flow for a single patch_all run is:
- You click Patch all on a Host Detail page. The Patch Wizard opens, pre-loaded with the host.
- You optionally override the policy (e.g. "Run immediately" on a host that has a delayed policy) and click the fire button.
- The browser calls
POST /patching/triggerwithpatch_type=patch_all. Becausepatch_allcannot be dry-run on Linux and FreeBSD, the run starts inpending_approvalif you ticked "Submit for approval", or goes straight toqueuedotherwise. - The server inserts a
patch_runsrow, snapshots the effective policy onto it, and enqueues arun_patchtask on thepatchingasynq queue. If the policy introduces a delay, asynq schedules the task for the future and the run status showsscheduled. - When the task dequeues, the server sends a
run_patchWebSocket message to the agent connected for that host. - The agent flips the run to
running, callsapt-get --with-new-pkgs upgrade -y(or the equivalent for the OS), and streams stdout/stderr back overPOST /patching/runs/{id}/outputin short chunks. On Debian and Ubuntu,--with-new-pkgslets the upgrade install packages it does not already have, which is what a kernel ABI bump needs. Without it apt holds those upgrades back and they would keep reappearing as outdated after every run. - The server fans each chunk out to any browsers subscribed to
GET /patching/runs/{id}/stream, and persists the combined output to the database. - On success the agent sends a final
completedstage with the authoritative shell output. The server marks the runcompleted, emits apatch_run_completednotification, and flags the host as "awaiting post-patch report" so the next inventory sync can update the package status. - The Run Detail page swaps the green Live pill for a subtle Awaiting inventory report pill, then for New report received once the agent sends its next scheduled inventory report and the system knows the on-host packages reflect reality.
See Running a Patch for the step-by-step operator walkthrough, and Patch History and Live Logs for everything to do with the terminal pane and log stream.
Related Documentation
- Running a Patch: step-by-step, from trigger through live log to "patched".
- Patch Policies and Scheduling: configure when patches actually run.
- Patch History and Live Logs: work with the Runs & History table and live terminal output.
- Release Notes: 2.0.0: the release that introduced the patching module.