ubuntu
Configuring unattended-upgrades on Ubuntu the way production actually needs it
Which security patches you want auto-applied, which ones you don't, and how we handle reboots across a fleet of thousands of servers.
13 de maio de 2026 · 8 min · por Sudhanshu K.
Configuring unattended-upgrades on Ubuntu the way production actually needs it
There are two failure modes for Ubuntu patching in production. The first is the one everyone worries about: an auto-applied update breaks a running service at 03:00 and nobody finds out until customers complain at 09:00. The second is the one that actually does the damage: nothing auto-applies, the fleet drifts for nine months, and a known-exploited CVE finally lands on a box that was missing a patch released the day after the last manual maintenance window.
In our managed Ubuntu fleet the second failure mode is roughly 50× more common than the first. So we run unattended-upgrades, but we run it carefully. This is the configuration we ship on every Ubuntu Server we manage, and the reasoning behind every line of it.
What unattended-upgrades actually does
unattended-upgrades is a Python script that gets invoked by an APT periodic timer. It reads a configuration file, decides which packages to upgrade, runs apt-get against just those sources, and optionally reboots. It is not a "set and forget" tool — the defaults from Canonical are reasonable for desktops and dangerous for servers.
The two files that matter:
/etc/apt/apt.conf.d/20auto-upgrades # when to run
/etc/apt/apt.conf.d/50unattended-upgrades # what to upgrade and howThe periodic timer — daily, not weekly
# /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";The values are days. 1 means "every day." We've seen guides that recommend 7 for the unattended-upgrade run "to avoid surprises." This is exactly backwards — running daily means the worst-case window between a security patch being available and being applied is ~24 hours, and the change set on any given day is small (usually 0-2 packages). Run weekly and you'll occasionally have a 50-package upgrade hit a box that hasn't been touched in a fortnight. Small frequent diffs are easier to debug than big rare ones.
What we let it upgrade — and what we don't
# /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
"linux-image-*";
"linux-headers-*";
"linux-generic*";
"docker-ce";
"docker-ce-cli";
"postgresql-1[0-9]";
"mysql-server-8.*";
"redis-server";
"nginx";
};The allowlist is the important half. We only auto-upgrade from the -security pockets, never from -updates, -backports, or -proposed. The -security pocket is patched by Canonical's security team and is designed to be ABI-stable within a release. -updates includes bug-fix releases that are not strictly security and have a much higher behaviour-change rate.
The blocklist matters more than people think. We pin:
- Kernel packages — handled by Livepatch (see the next article in this series) for CVE patching without reboot, and by scheduled maintenance windows for full kernel updates. We don't want an automatic kernel update changing the running kernel and waiting for a reboot we didn't plan.
- Docker — the daemon restarts on upgrade, which kills every container on the host. Coordinated separately.
- Database servers — Postgres and MySQL minor upgrades occasionally require migration steps. Never auto.
- Redis — same as databases; a restart drops cache, which is a customer-visible event on some workloads.
- Web server frontends —
nginxupgrades reload the workers cleanly in theory; in practice we want to coordinate with the deploy pipeline.
For everything else — openssl, glibc, bash, sudo, the language runtimes, the hundred-odd transitive deps — yes, auto-patch from -security. The risk of leaving a glibc CVE unpatched for two weeks vastly outweighs the risk of glibc being repackaged and breaking something. (In ten years on Ubuntu LTS, we have seen this break a running production service exactly twice. Both times the fix was a service restart.)
ESM and Pro — yes, you want it
The ${distro_id}ESMApps and ${distro_id}ESM lines refer to Ubuntu Pro's Expanded Security Maintenance pockets. If you're not on Pro, those lines are silently ignored. If you are, they unlock security patches for ~25,000 packages in universe that Canonical doesn't ship in the default -security pocket.
For a server running anything from universe (which is most fleets — redis-tools, htop, plenty of language libraries), Pro is the difference between "we patch the OS" and "we patch the whole OS." It's free for personal use up to 5 machines and modestly priced at fleet scale. We recommend it for every customer on Ubuntu, and we ship it by default on our managed Ubuntu tier.
(If you're on RHEL or AlmaLinux, the equivalent is dnf-automatic with upgrade_type=security. We cover that separately in our RHEL guide, and the principles are the same — narrow the scope, log everything, control the reboot.)
The reboot policy — this is where most teams get it wrong
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";We default Automatic-Reboot to false for production servers, and instead consume the /var/run/reboot-required flag from our orchestration tooling. Here's why:
- Auto-reboot at 03:30 sounds fine until you have a fleet spanning AEST, AWST, UTC and PDT, and your "off-peak" for one customer is somebody else's lunch rush.
- An unexpected reboot of a primary database, even one we believe is replicated, is a non-trivial event. We want a human to confirm "yes, this replica is healthy" before failing over.
- Many security patches don't actually require a reboot — only kernel and a small set of core libraries (glibc via
needrestart) do. Rebooting unnecessarily is operational risk for no security gain.
What we do instead: a daily Ansible run checks each host for /var/run/reboot-required, tags it, and the on-call engineer (or, for low-criticality tiers, an automation that respects a maintenance window calendar) drains traffic and reboots. For a fleet of a few hundred servers, this is maybe 10-15 reboots a week, fully tracked, fully observable.
For non-customer-facing nodes (CI runners, batch workers, dev environments), we do enable automatic reboot — those don't need a human in the loop and the simpler config is fine.
Logging and observability — never trust silent success
Unattended-Upgrade::Mail "ops@example.com";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::SyslogEnable "true";
Unattended-Upgrade::SyslogFacility "daemon";
Unattended-Upgrade::Verbose "true";Every run logs to syslog with facility daemon, which our log shipper forwards to the central SIEM. The logs go into a unattended-upgrades index, and we have a saved query that surfaces any run that didn't end in Packages that were upgraded: ... followed by a clean exit. The most common failure mode is a held package or a dpkg lock — both visible immediately.
For paranoia, we also alert on the absence of an unattended-upgrades log line for any host that hasn't reported in 36 hours. A box that's stopped patching is a box that's also stopped doing something else useful.
The dry-run that everyone forgets
Before rolling a config change to the fleet, run it on one canary host with --dry-run:
sudo unattended-upgrade --dry-run --debug 2>&1 | lessThe output shows you exactly which packages it would touch, which it would skip and why, and whether your blocklist regexes actually match what you think they do. We've caught at least three production-bound config typos this way — the most memorable being a misspelled Package-Blacklist that silently allowed linux-image-* through.
Putting it together
A reasonable production /etc/apt/apt.conf.d/50unattended-upgrades for an Ubuntu 24.04 server, in full:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
"linux-image-.*";
"linux-headers-.*";
"linux-generic.*";
"docker-ce.*";
"postgresql-.*";
"mysql-server-.*";
"redis-server";
"nginx";
};
Unattended-Upgrade::DevRelease "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Dependencies "false";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Mail "ops@example.com";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::SyslogEnable "true";
Unattended-Upgrade::MinimalSteps "true";Combined with the daily timer in 20auto-upgrades, this gives you: same-day security patches from trusted pockets, no surprise kernel changes, no surprise reboots, full audit trail, and a controlled upgrade footprint that doesn't pull in random ABI breakage.
If you'd rather not own this layer at all, our management tier runs exactly this configuration across every customer fleet — plus the reboot orchestration, the SIEM ingestion, the maintenance-window calendaring, and the on-call coverage when something does go sideways.
Sudhanshu K. is a senior SRE at EdgeServers (RemotIQ Pty Ltd, ABN 91 682 628 128). He has been running Ubuntu in production since 8.04 and has strong opinions about apt pinning syntax.