Software updates¶
The Software updates policy type (technical name Updates) manages
the unattended update mechanism of every supported distribution and the
reboot that follows updates: what a node installs without a person, on
which schedule, which packages are held back, and when the node restarts.
Every node reports the updates it can install, with their advisories, and
whether it needs a reboot. This page explains what the agent changes on
the endpoint, how to build a policy in the editor, how several policies
are merged, and how to verify and troubleshoot the result.
The Package type is for named packages
The Package type manages repositories and the state of named packages (installed, latest, removed). The Software updates type manages everything else: the distribution's own update engine and the reboot. The two work together; a package the Package type keeps at latest is updated on every sync regardless of the update schedule.
What it controls¶
- Mode - what the node installs on its own: nothing (the engine's timer is off), check only (download and report), security updates or all updates.
- Schedule - when the engine runs, as a systemd calendar expression with a random delay to spread a fleet, written as a drop-in of the distribution's timer.
- Reboot - every engine's own reboot is switched off and the agent decides: never, when an update needs it, or after every run; within a daily window; only when nobody is logged in, unless the reboot has been pending for a number of days; with a desktop warning before a reboot that affects users.
- Holds - packages the engine must not upgrade.
- Engine - the engine package is installed when missing, and a node without an engine can count as non-compliant.
- Limits - compliance thresholds in days: a security update pending longer than the limit, a reboot pending longer than the limit, or package metadata older than the limit makes the node non-compliant.
- Firmware - fwupd's daily metadata refresh, and whether pending firmware updates count against compliance. Firmware is never installed by policy.
Snap packages are out of scope: Bor does not manage snapd.
The engine per distribution:
| Distribution | Engine | Configuration Bor writes | Timer |
|---|---|---|---|
| Fedora 41 and later | dnf5-plugin-automatic |
/etc/dnf/automatic.conf |
dnf5-automatic.timer |
| RHEL 8 to 10, Rocky, AlmaLinux, CentOS Stream | dnf-automatic |
/etc/dnf/automatic.conf |
dnf-automatic.timer |
| Debian, Ubuntu | unattended-upgrades |
/etc/apt/apt.conf.d/90bor-updates |
apt-daily-upgrade.timer |
| openSUSE Leap 15.6 and 16, Tumbleweed, SLES 16 | os-update |
/etc/os-update.conf and zypper locks |
os-update.timer |
| openSUSE MicroOS, Aeon, Kalpa, SLE Micro (read-only root) | transactional-update |
/etc/transactional-update.conf and zypper locks |
transactional-update.timer |
| Fedora Kinoite, Silverblue, IoT (rpm-ostree) | rpm-ostree's automatic update policy | /etc/rpm-ostreed.conf |
rpm-ostreed-automatic.timer |
Image systems update as a whole: security updates reports as
inapplicable there and the node stays in check mode, and holds do not
apply. On transactional systems security updates means
transactional-update patch (every patch) and all updates dup on
rolling systems or up on others; the update lands in a new snapshot
that the reboot rule boots into.
The agent picks the engine from the system; updates.engine in the
agent configuration forces one when the detection is wrong for a node.
Everything the agent changes is recorded in
/var/lib/bor/agent/updates-state.json (the original configuration file
is kept under /var/lib/bor/agent/updates-backups) and restored when the
node leaves every policy.
Endpoint prerequisites¶
- A package manager the agent knows: dnf, apt, zypper, transactional-update or rpm-ostree. Other nodes report the policy as inapplicable (or non-compliant with Treat nodes without an engine as non-compliant).
- The engine package (
dnf5-plugin-automatic,dnf-automatic,unattended-upgrades,os-update): installed through PackageKit when Install the engine when it is missing is on, which is the default. - systemd reachable on the system bus, for the timer and the drop-in.
- A reboot signal. The agent compares the running kernel with the
newest installed one everywhere, and asks
needs-restarting(dnf),needrestartand/run/reboot-required(Debian, Ubuntu),zypper needs-rebooting(SUSE) or the staged deployment (rpm-ostree). The agent's deb package depends onneedrestart; the rpm package recommends the dnf plugins. - loginctl for the logged-in users rule, and a desktop session with a notification server for the warning.
Agent configuration¶
| Key | Default | Description |
|---|---|---|
updates.state_path |
/var/lib/bor/agent/updates-state.json |
Record of every original state, restored when the node leaves every policy. |
updates.backup_dir |
/var/lib/bor/agent/updates-backups |
Backups of the engine configuration files Bor replaced. |
updates.reconcile_interval |
10m |
Interval of the pass that re-asserts the timer and the configuration while a policy applies; 0 disables it. |
updates.inventory_interval |
1h |
How often the node re-sends its update state while connected. |
updates.reboot_check_interval |
1m |
How often the reboot rule runs while a policy applies. |
updates.tool_timeout |
10m |
Maximum run time of one engine or package manager call. |
updates.allow_install |
true |
Let policies install the engine package. With false a missing engine reports inapplicable. |
updates.allow_reboot |
true |
Let policies reboot the node. With false reboot rules report inapplicable and the node never reboots on its own. |
Create a Software updates policy¶
- Go to Policies -> Create a policy and choose Software updates in the System & security group.
- Pick a node from Show the state of at the top of the editor to see its engine, pending updates and reboot state.
- Fill the tabs described below, or start from a template, review the summary and save. Release the policy and bind it to a node group; a pilot group with a tighter schedule is the usual first step.

Editor sections¶
- Mode
- The five modes with one sentence each, and three switches: install the engine when missing, treat nodes without an engine as non-compliant, remove dependencies updates left unused. With the security or all mode a fourth switch installs the updates offline at the reboot (see below).
- Schedule
- A preset or a calendar expression, the random delay and whether a run missed while the node was off is caught up.
- Reboot
- The reboot rule, the window, the logged-in users switch, the force period, the warning minutes and the message. Allowing forced reboots, or reboots with users logged in, asks for a confirmation because unsaved work is lost.
- Holds
- Package names or patterns with
*and?. - Limits
- The three age thresholds, in days; 0 leaves a threshold unchecked. The ages are measured on the node: how long each security update has been pending there since the agent first saw it, since when a reboot is pending, and when the engine last refreshed its metadata.
- Firmware
- Keep, enable or disable
fwupd-refresh.timer, and whether pending firmware updates are reported as non-compliant.
Templates: Workstations: security updates nightly, reboot in the window, Servers: all updates, no automatic reboot, Check only: report pending updates.
Setting reference¶
| Setting | Field | Values | What the agent does |
|---|---|---|---|
| Mode | mode |
UPDATES_MODE_OFF, _CHECK, _SECURITY, _ALL |
renders the engine configuration and enables or disables its timer |
| Schedule | schedule.onCalendar, .randomizedDelay, .persistent |
calendar expression, time span, bool | writes <timer>.d/90-bor-updates.conf |
| Reboot | reboot.policy |
UPDATES_REBOOT_NEVER, _WHEN_NEEDED, _ALWAYS |
runs the reboot rule every minute |
| Reboot window | reboot.window |
HH:MM-HH:MM, local time, wraps past midnight |
reboots only inside the window |
| Logged-in users | reboot.onlyWithoutSessions |
bool, default on | no reboot while a graphical or SSH session exists |
| Force | reboot.forceAfterDays |
0 to 365 | reboots despite users once the reboot has been pending this long |
| Warning | reboot.warnMinutes, reboot.message |
0 to 1440, one line | desktop notification before a reboot that affects users |
| Hold | hold[] |
name or pattern | excludepkgs (dnf), Package-Blacklist (apt), zypper addlock |
| Unused dependencies | removeUnusedDependencies |
bool | apt Remove-Unused-Dependencies |
| Engine | installEngine, requireEngine |
bool | installs the engine package; non-compliant without one |
| Offline install | offline |
bool, with the security or all mode | the engine only downloads; the agent stores a dnf5 offline transaction and the reboot installs it |
| Limits | limits.securityMaxAgeDays, .rebootMaxAgeDays, .checkMaxAgeDays |
0 to 3650 days | evaluates the age items after every inventory scan |
| Firmware | firmware.refreshMetadata, firmware.reportPending |
bool | enables or disables fwupd-refresh.timer (its state is recorded for the restore); reports pending firmware |
Example: workstations¶
{
"mode": "UPDATES_MODE_SECURITY",
"schedule": {"onCalendar": "*-*-* 02:00", "randomizedDelay": "1h", "persistent": true},
"reboot": {"policy": "UPDATES_REBOOT_WHEN_NEEDED", "window": "03:00-05:00", "forceAfterDays": 7, "warnMinutes": 10},
"hold": ["linux-image-*"]
}
Example: offline installs on Fedora workstations¶
{
"mode": "UPDATES_MODE_SECURITY",
"offline": true,
"schedule": { "onCalendar": "*-*-* 02:00", "randomizedDelay": "1h" },
"reboot": { "policy": "UPDATES_REBOOT_WHEN_NEEDED", "window": "03:00-05:00", "forceAfterDays": 7, "warnMinutes": 10 }
}
On Fedora 41 and later with dnf5 the engine's timer only downloads the
security updates; the agent stores them as a dnf5 offline transaction
(dnf5 upgrade --offline --security) and the reboot of the window runs
dnf5 offline reboot, so the packages are installed before the desktop
comes up and no running program sees a half-applied update. A stored
transaction counts as a pending reboot. On every other engine the
Offline install item reports inapplicable and the updates install
live.
Example: a pilot ring¶
Two policies, two groups. The pilot group gets every update a day after release and a tight limit; the fleet follows a week later:
| Pilot (group "Pilot workstations") | Fleet (group "Workstations") | |
|---|---|---|
| Mode | all updates | security updates |
| Schedule | *-*-* 02:00, delay 1h |
Mon *-*-* 02:00, delay 6h |
| Reboot | when needed, window 03:00-05:00, force after 3 days |
when needed, window 03:00-05:00, force after 7 days |
| Limits | security 3 days, reboot 3 days, check 2 days | security 14 days, reboot 10 days, check 3 days |
A node in both groups gets the stricter of the two: the higher binding priority wins per key, and on equal priority the mode that installs more and the reboot rule that reboots more win, while each policy reports its own limits. Problems show up on the pilot's Compliance page days before the fleet installs.
Example: servers¶
{
"mode": "UPDATES_MODE_ALL",
"schedule": {"onCalendar": "*-*-* 04:00", "randomizedDelay": "30m"},
"reboot": {"policy": "UPDATES_REBOOT_NEVER"}
}
Merge behavior¶
Several Software updates policies bound to one node merge per key by binding priority. On equal priority the mode that installs more wins (all over security over check over off), the reboot rule that reboots more wins as a whole (always over when needed over never, with its window and switches), holds are a union, Remove unused dependencies and Treat nodes without an engine as non-compliant are on when any policy sets them, and the engine is installed unless a policy switches it off. Two schedules with different expressions at equal priority are a conflict: neither applies and both policies report an error item. The fwupd refresh setting of the highest priority policy applies; pending firmware is reported when any policy asks. Limits are evaluated per policy: each policy reports its own thresholds, so two policies with different limits both report, each against its own.
What the agent changes on the node¶
| Path or state | Written by | Watched |
|---|---|---|
/etc/dnf/automatic.conf, /etc/os-update.conf, /etc/rpm-ostreed.conf (the original is backed up and restored) |
the mode | yes |
/etc/apt/apt.conf.d/90bor-updates |
the mode (apt lists are cleared and re-declared there) | yes |
/etc/transactional-update.conf (the original is backed up and restored) |
the mode | yes |
the dnf5 offline transaction under /usr/lib/sysimage/libdnf5/offline |
the offline switch (removed when the policy leaves) | no |
/etc/systemd/system/<timer>.d/90-bor-updates.conf |
the schedule | yes |
| the engine timer's enabled state | the mode | reconciled |
fwupd-refresh.timer's enabled state |
the firmware setting | reconciled |
| zypper package locks | holds on SUSE | reconciled |
/var/lib/bor/agent/updates-state.json, /var/lib/bor/agent/updates-backups/ |
every original state | yes |
Every file Bor writes starts with the line Managed by Bor - changes are
overwritten (after the comment marker of its syntax).
The reboot¶
A reboot is pending when any signal says so: a newer kernel than the
running one, needs-restarting or needrestart, /run/reboot-required,
zypper needs-rebooting, transactional-update's /run/reboot-needed, a
stored dnf5 offline transaction, or a staged rpm-ostree deployment. The agent
records when it first saw it, shows it on the node page and in the
Reboot rule item, and with Reboot when an update needs it:
- waits for the window;
- waits until nobody is logged in, unless the reboot has been pending longer than Force after days;
- warns logged-in users through a desktop notification Warning minutes before a forced reboot;
- reboots through
dnf5 offline rebootwhen an offline transaction is stored, throughrebootmgrwhen SUSE's reboot manager runs, otherwisesystemctl reboot, and records the reboot as a tamper-style event with the resourceupdates:rebootand the reasons.
Reboot after every update run does the same after any run that installed something. Never reboot only reports.
Drift handling¶
Files Bor writes are guarded by the tamper watcher. The timer state is
re-asserted by the reconcile pass every updates.reconcile_interval;
each correction is a tamper event with the resource updates:timer:<unit>
or updates:config:<file>.
Removal and restore¶
When a policy is unbound, archived or deleted, the agent restores the engine's configuration from its backup (or removes the file it created), removes the schedule drop-in, returns the timer to the enabled state it had, and removes the zypper locks it added. The state file keeps only the reboot bookkeeping (since when a reboot is pending) and goes away with the next reboot. The engine package stays installed.
The node details¶
The Software updates section of a node shows the engine and its effective mode, who configured it (Bor, the vendor defaults or someone else), the timer state, the last check and the last run with its outcome, the running and newest installed kernel, whether a reboot is pending and why, and the pending updates with their advisories, severities and CVE ids, searchable and filterable to security updates, with pending firmware updates when fwupd knows of any. Other update mechanisms the node runs (PackageKit's background refresh, YaST's online update job) are named.
Holders of updates:operate get three actions there:
- Check for updates refreshes the package metadata (
dnf makecache,apt-get update,zypper refresh,rpm-ostree upgrade --check) and reports the inventory again. - Install updates now runs the engine once with the policy's mode,
the way its timer would (
dnf5 automatic,dnf-automatic,unattended-upgrade,os-update,rpm-ostree upgrade), waits for it and shows the tail of its output. Refused when the policy's mode installs nothing. An engine run can take several minutes; the request waits up to fifteen. - Reboot restarts the node at once when nobody is logged in. With a
user logged in it is refused unless forced: then every graphical
session gets a desktop warning and the node restarts after one minute
(
shutdown -r +1).
Every action is audit-logged as updates.action with the kind, the
outcome and the agent's message; a reboot also records a tamper-style
event with the resource updates:reboot. The agent refuses installs with
updates.allow_install: false and reboots with updates.allow_reboot:
false.
The nodes list has an Updates column (security updates pending, a
reboot pending, no engine, up to date) and a filter with the same values
plus Not reported. Export pending updates and Security only
there download a CSV of every pending update of the listed nodes (one
row per node and package: node, groups, engine, kind, package, version,
advisory, severity, security, CVE ids, reported at) for the
vulnerability-handling record; the node section has the same export for
one node. The API is GET /api/v1/updates/export?format=csv|json
with security=1, group=, node= and q= filters, within the
caller's node scope. The dashboard's fleet overview counts the nodes with
security updates pending, with a reboot pending and without an engine,
each linking to the filtered list.
Compliance¶
| Schema id | Compliant when |
|---|---|
updates:engine |
the engine is installed (inapplicable when the policy does not install it, non-compliant when it is required) |
updates:mode |
the configuration and the timer match the mode |
updates:schedule |
the drop-in is in place |
updates:reboot |
the reboot rule is applied; the message says since when a reboot is pending |
updates:hold |
the hold is in the configuration or the lock exists |
updates:last-run |
the engine's last run succeeded (error with its message otherwise) |
updates:security-age |
no security update has been pending on the node longer than the limit; the message names the oldest ones. Inapplicable until the first inventory scan |
updates:reboot-age |
no reboot has been pending longer than the limit |
updates:check-age |
the engine refreshed its metadata within the limit; non-compliant when no refresh is found |
updates:firmware |
refresh: the fwupd timer is in the requested state; pending: no firmware update is pending (inapplicable without fwupd) |
updates:offline |
the offline transaction is stored when updates are pending, or there is nothing to stage (inapplicable without dnf5 or outside the security and all modes) |
The age items read what the inventory scan recorded (every
updates.inventory_interval, after every sync and after every action),
so a scan is followed by a compliance report when a policy sets a limit.
On the Compliance page the mode and the age items carry the CIS control
Ensure updates, patches, and additional security software are
installed (1.2.2.1 in CIS Red Hat Enterprise Linux 9 Benchmark v2.0.0
and CIS Ubuntu Linux 24.04 LTS Benchmark v1.0.0).
Verify on the endpoint¶
# The engine's configuration and timer
cat /etc/dnf/automatic.conf /etc/apt/apt.conf.d/90bor-updates /etc/os-update.conf 2>/dev/null
systemctl list-timers 'dnf*' 'apt-daily*' 'os-update*' 'rpm-ostreed*'
cat /etc/systemd/system/*/90-bor-updates.conf
# Pending updates and the reboot signals
dnf5 -C check-upgrade; dnf5 needs-restarting
apt list --upgradable; needrestart -b; cat /run/reboot-required.pkgs
zypper list-patches; zypper needs-rebooting
rpm-ostree status
# What the agent recorded
cat /var/lib/bor/agent/updates-state.json
journalctl -u bor-agent | grep -i "software updates"
Troubleshooting¶
The engine item says the package is not available. The node's repositories do not carry it, or PackageKit is not running. Install it by hand or add the repository with a Package policy.
The last run item is an error with "Lock could not be acquired". Another package manager held the lock when the engine ran; the next run retries. PackageKit keeps the lock for a while after its own transactions.
A reboot is pending but the node never reboots. Check the Reboot rule item and the node page: the policy may say never, the window may not have come, or a user is logged in and the force period has not passed. The agent also waits ten minutes after a reboot it started itself.
The offline item says "storing the offline transaction" failed.
The download failed or the repositories were unreachable; the next
reconcile pass (every ten minutes) tries again. dnf5 offline status and
dnf5 offline log on the node show the stored transaction and the
outcome of the last one.
A transactional system reports os-update as its engine.
The detection needs a read-only root; on a read-write snapshot system
set updates.engine: transactional-update in the agent configuration.
Security updates on Kinoite reports inapplicable. Image systems cannot select updates; choose all updates for them, in a separate policy bound to their group if the rest of the fleet installs security updates only.
Security considerations¶
- A hold keeps a vulnerable package on every node of the group. Creating,
saving, releasing or importing a policy with holds is audited as
policy.updates_holdwith the patterns. - Unattended reboots can lose users' work. Forced reboots and reboots with
users logged in are off by default, need a confirmation in the editor,
always warn users first, and can be refused per node with
updates.allow_reboot: false. - The agent never runs administrator-supplied commands for this type; it configures the distribution's engine and runs that engine's own units.
- The inventory carries package names, versions and advisory ids, no user data; the pending list is capped at 200 entries per node.
- The node actions change the node at once and need
updates:operate, which only Super Admin holds on a new installation; every request is audited with the actor and the outcome. A forced reboot warns the users first and is refused by nodes withupdates.allow_reboot: false.