LinuxXXXIII · Fleet Patch ManagementStrategy
Fleet patch strategy - waves, canaries, and rollback
What you'll learn
- Design a fleet patch strategy
- Apply patches in waves
- Validate health at each wave
- Roll back on regression
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-09
Patching a fleet is not “apt upgrade on every host”. It is a disciplined wave-based rollout with health validation and rollback capability.
The wave strategy
Wave 1: dev / staging (immediate)
Wave 2: canary (5-10% of production)
Wave 3: production (50%)
Wave 4: production (remaining)
Each wave has a hold period (15-60 minutes) to validate health before proceeding to the next wave. That hold has to be implemented by something - a pause step, a scheduler, a human gate. No tool gives it to you for free.
Apply via configuration management
Use Ansible, Puppet, or similar to apply patches:
# Ansible playbook
- name: Apply security patches
hosts: all
serial: "5%"
max_fail_percentage: 0 # any host failure aborts the whole rollout
tasks:
- name: Security-only upgrade
ansible.builtin.apt:
upgrade: safe # never removes packages; dist can
update_cache: yes
cache_valid_time: 3600
environment:
DEBIAN_FRONTEND: noninteractive
- name: Detect pending reboot (Debian/Ubuntu)
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: Reboot if the kernel or libc changed
ansible.builtin.reboot:
reboot_timeout: 600
when: reboot_required.stat.exists
- name: Verify service health from outside the host
ansible.builtin.uri:
url: "https://{{ inventory_hostname }}/health"
status_code: 200
register: health
until: health.status == 200
retries: 10
delay: 15
delegate_to: localhost
- name: Soak before the next batch
ansible.builtin.pause:
minutes: 15
On RHEL, replace the stat check with a needs-restarting -r
command task: exit code 1 means a reboot is required.
serial: "5%" rolls out to 5% of hosts at a time. It provides
batching only - the delay between batches is the explicit
pause task, and the abort gate is max_fail_percentage.
Health validation
After each wave, check:
- Service is responding (HTTP health endpoint), probed from outside the host.
- The running kernel matches the newest installed kernel:
uname -ragainst/boot/vmlinuz-*. - No pending reboot:
/var/run/reboot-requiredabsent, orneeds-restarting -rexits 0. - No process still mapping a replaced library:
needrestart -b(Debian/Ubuntu) orneeds-restarting -s(RHEL) returns an empty service list. - No failed units:
systemctl list-units --state=failed. - No new errors in journald / auditd.
- CPU, memory, network within normal range.
- Synthetic monitoring passes.
The first four are what separate installed from remediated.
A vulnerability scanner reads package versions and will call a
patched-but-not-rebooted host compliant. It is not.
linux-checklist-post-patching-validation carries the full list
with commands.
Automate the validation:
# After a patch wave, check the error rate.
# --data-urlencode does the escaping: a bare
# ...?query=rate(errors[5m]) is a shell syntax error, because ( ) and [ ]
# are shell metacharacters, and the URL would be malformed even if it ran.
error_rate=$(curl -sG http://prometheus:9090/api/v1/query \
--data-urlencode 'query=sum(rate(http_requests_total{code=~"5.."}[5m]))' \
| jq -r '.data.result[0].value[1] // "0"')
# bc, not [[ ]] - bash cannot compare floating-point numbers
if (( $(echo "$error_rate > 0.01" | bc -l) )); then
echo "Error rate too high: $error_rate - halting the rollout" >&2
exit 1
fi
Rollback
Have a rollback plan for every patch:
- Packages:
sudo dnf history undo <id>orsudo apt-get install <package>=<old-version>. Useundo, notrollback:dnf history undo <id>reverses that one transaction, whilednf history rollback <id>reverses every transaction performed after<id>, sweeping up unrelated security updates applied since.linux-rollback-strategiescovers the distinction. - Containers: redeploy the previous image.
- Configuration: revert the configuration management state.
Test rollback in staging: deploy the patch, deploy the rollback, verify both work.
Maintenance windows
For non-emergency patches, schedule maintenance windows:
- Off-peak hours (typically nights or weekends).
- Pre-announced to stakeholders.
- Limited duration (2-4 hours).
- Rollback ready.
The window is a contract: the change starts at T+0, rolls forward at T+30 minutes, completes by T+window-end. If something goes wrong, rollback by T+window-end.
Knowledge check
Knowledge check · 6 questions
Q1. What is the first wave of a fleet patch?
Q2. Patches should always be applied to all hosts at once.
Q3. Which of the following are valid wave strategies? Select all that apply.
Q4. Your playbook uses serial: "5%" and a health-check task, and the canary batch fails its health check. With default Ansible settings, what happens next?
Q5. A wave completes. Every host reports the fixed openssl package installed, and the scanner shows the CVE remediated. What still has to be verified before signing the wave off?
Q6. serial: "5%" inserts a pause between batches, so no explicit soak step is needed.
Passing score: 75%. Answers are checked in this browser.