LinuxXXXIII · Fleet Patch ManagementAutomation
Automated patching in a fleet - what to automate and what never to
What you'll learn
- State the trade-off automated patching makes against staged rollout
- Decide per host tier what may be automated and what may not
- Keep automated patching wave-aware rather than simultaneous
- Configure splay, blocklists and reboot policy as guardrails
- Build a tested kill switch for automated patching
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11
The first four lessons in this part built a staged rollout: canary, waves, health validation between them, rollback if a wave regresses. Then somebody enables unattended upgrades across the fleet, and every host that is powered on at 06:00 applies the same change inside the same hour.
Both are defensible. They are not compatible by default, and
the reconciliation is the subject of this lesson. The mechanics
of configuring unattended-upgrades and dnf-automatic are
covered in the packages part; what follows is the decision
about where to point them and what to fence off.
What automation buys and what it costs
| Buys | Costs |
|---|---|
| Patch latency falls from weeks to hours | The canary is gone: every host is wave one |
| No host is forgotten | Failures happen when nobody is watching |
| Fleet converges to one package state | Restarts and reboots land unattended |
| No human effort per cycle | The change has no change record anybody read |
The first column is not marketing. Most real compromises exploit vulnerabilities that had a patch available for weeks, and the single largest cause of that is a manual process nobody had time for. Automation is usually the right default.
The second column is why it is a default and not a policy.
The decision, per tier
Automation is appropriate when three things hold: the change is small, the reversal is cheap, and the failure is detectable without a human. Score each tier of the fleet against those.
| Tier | Update class | Service restart | Reboot |
|---|---|---|---|
| Developer workstations, lab | Everything | Automatic | Automatic |
| Dev / CI hosts | Everything | Automatic | Automatic, off-hours |
| Staging | Security only | Automatic | Scheduled, announced |
| Production, stateless | Security only | Automatic, staggered | Never automatic |
| Production, stateful or quorum-bearing | Security only | Never automatic | Never automatic |
The line that matters is the last one. A database primary, a Corosync node, a Pacemaker-managed resource or an etcd member must not have a service restarted or a host rebooted by a timer that has no idea what quorum is. Two nodes of a three-node cluster rebooting inside the same hour is a quorum loss, and the timer will happily do it because both hosts are running the same configuration.
Making automation wave-aware
The way to have both is to stop treating the schedule as the thing that stages the rollout, and let the repository stage it instead.
Each tier points at a different snapshot of the same repository. Automation runs everywhere on the same schedule, but dev is consuming this week’s packages and production is consuming the set that dev validated a week ago. Promotion is a single change to which snapshot a tier resolves - not a change to any host.
upstream security repo
|
v
snapshot 2026-08-11 <-- dev, CI (automation on)
snapshot 2026-08-04 <-- staging (automation on)
snapshot 2026-07-28 <-- production (automation on)
This inverts the usual arrangement in a useful way. The waves are still waves, the canary is still a canary, and no host has a bespoke schedule. The next lesson covers how to build and promote those snapshots.
Until you have that, the poorer substitute is different schedules per tier, which works but couples the rollout to wall clock time and gives you no way to say “production installs exactly what staging installed”.
Guardrails
Splay
Debian’s timers already randomise, and the values are worth knowing because they are what stops a fleet stampede on your mirror:
$ systemctl cat apt-daily-upgrade.timer# /usr/lib/systemd/system/apt-daily-upgrade.timer
[Unit]
Description=Daily apt upgrade and clean activities
After=apt-daily.timer
[Timer]
OnCalendar=*-*-* 6:00
RandomizedDelaySec=60m
Persistent=true
[Install]
WantedBy=timers.targetAn hour of jitter spreads a few hundred hosts adequately. For thousands, widen it with a drop-in rather than editing the shipped unit:
sudo systemctl edit apt-daily-upgrade.timer
# [Timer]
# RandomizedDelaySec=6h
systemctl show apt-daily-upgrade.timer -p RandomizedDelaySec
systemctl list-timers apt-daily-upgrade.timer
Persistent=true deserves attention too: a host that was off
at 06:00 runs the job as soon as it boots, jitter and all. That
is correct behaviour for patching and surprising behaviour if
you were relying on the window.
The RHEL-family equivalent is random_sleep in
/etc/dnf/automatic.conf, plus the same RandomizedDelaySec
on dnf-automatic.timer.
Scope
Restrict what automation is allowed to install. On Debian this is the origins pattern - security only, for anything above staging - plus an explicit blocklist for packages whose upgrade is a change you want to make deliberately:
grep -n -A8 'Unattended-Upgrade::Package-Blacklist' \
/etc/apt/apt.conf.d/50unattended-upgrades
Kernels, the database server, the container runtime and anything with a config-file migration belong on that list. So does anything you have pinned, because a pin and a blocklist answer different questions and you want both.
Version holds are the coarser hammer and survive a configuration mistake:
sudo apt-mark hold postgresql-15
apt-mark showhold
# RHEL family
sudo dnf versionlock add postgresql-server
dnf versionlock list
Reboot policy
The dangerous default is not “reboot”, it is “reboot without knowing whether this host may reboot right now”.
// /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
Leave Automatic-Reboot false above the dev tier and let the
rolling-maintenance process own reboots, because that process
is the only thing that knows about quorum, drain order and
active sessions. The reboot-requirement lesson in the
vulnerability part covers detecting that a reboot is pending;
the point here is who decides to act on it.
Observability
Automation with no reporting is a process nobody is running. Three things to collect from every host:
# What was actually installed, and whether it errored
sudo tail -n 40 /var/log/unattended-upgrades/unattended-upgrades.log
sudo grep -c ERROR /var/log/unattended-upgrades/unattended-upgrades.log
# The transaction record, from the package manager itself
grep ' upgrade ' /var/log/dpkg.log | tail -20
sudo dnf history list | head
# Did the last run succeed, and when was it
systemctl status apt-daily-upgrade.service --no-pager
systemctl list-timers --all apt-daily-upgrade.timer
Turn the last one into a metric. “Time since last successful automated patch run” per host is the single number that catches the failure mode automation is worst at: a host where the timer has been failing silently for four months and everyone assumed it was covered.
Verify without changing anything before you trust a new configuration:
sudo unattended-upgrade --dry-run --debug
The kill switch
Before enabling automation on production, answer this: a bad package is in the repository right now, and hosts are picking it up. How do you stop the fleet in the next five minutes?
Have an answer that does not require touching hosts one at a time:
# Per host - fine for a handful, useless for a fleet
sudo systemctl mask --now apt-daily-upgrade.timer
# Fleet-wide, through configuration management, is the real answer
# ansible -i inventory all -m systemd \
# -a "name=apt-daily-upgrade.timer enabled=no state=stopped masked=yes" -b
If your repository is snapshotted, the faster switch is to repoint the tier at the previous snapshot, which also fixes the hosts that already have the mirror index cached.
Test the kill switch on a schedule. An untested kill switch is a plan, and plans fail on the day the SSH bastion is one of the hosts that took the bad update.
Knowledge check
Knowledge check · 5 questions
Q1. What does enabling unattended upgrades fleet-wide do to a canary strategy?
Q2. Which host tier is the clearest case for never allowing automated reboots?
Q3. Which of these are guardrails on automated patching rather than the automation itself? Select all that apply.
Q4. A host where the patching timer has been failing for months will show up in the normal vulnerability scan, so no separate metric is needed.
Q5. Automated patching upgrades a shared library at 03:00 and a daemon fails to restart. Why does the investigation usually take longer than it should?
Passing score: 75%. Answers are checked in this browser.