Set It and Forget It: Automating Linux Patches with unattended-upgrades

Security updates while you sleep

Contents

The most common way servers get compromised is not some dazzling zero-day wielded by a nation-state. It is a known vulnerability, with a published fix that has been sitting in the distribution’s repositories for weeks, on a box where nobody ran the update. I have cleaned up after exactly this more than once — a forgotten VPS, a “temporary” test host that quietly became load-bearing, a machine everyone assumed someone else was maintaining. Every time, the fix had been available for ages. Patching is unglamorous, trivially easy to defer, and quietly catastrophic when neglected. The only reliable answer is to take human procrastination out of the loop entirely — and on Debian and Ubuntu, the tool that does it is unattended-upgrades.

Why unpatched systems get owned

Advertisement

When a vulnerability is disclosed and a patch ships, a clock starts. Attackers read the same advisories you do, and automated scanning tools begin probing the public internet for unpatched hosts within hours, sometimes minutes. Mass-exploitation of a freshly disclosed bug is now measured in days, not the weeks it once was — the moment a proof-of-concept lands on the internet, opportunistic scanners fold it into their kit. The window between “fix available” and “fix applied” is exactly the window in which you are exposed to a problem that is already solved. There is no worse category of breach than one you could have prevented by typing apt upgrade.

It is worth being clear-eyed about the pattern, because the failure is almost never technical — it is behavioural. We intend to patch, we mean to get to it, and then something more urgent always comes along. The same reflex that leaves kernel exploits viable long after they are public — a theme I dug into in what Linux kernel exploits keep teaching us — is at work here: the vulnerability class changes, but the human tendency to defer the boring maintenance does not. Automation wins because it does not get distracted, does not deprioritise, and does not go on holiday.

The patch-versus-stability tension

If automatic patching is so obviously good, why doesn’t everyone do it? Because updates occasionally break things. A new package version changes a default, a kernel update upsets a driver, a library bump nudges an application into misbehaviour. Operators who have been burned by a bad update learn to be cautious, and that caution hardens into “we’ll patch manually, carefully” — which in practice becomes “we’ll patch eventually, maybe”.

The resolution is nuance rather than abstinence. Security updates are low-risk and high-value: they tend to be minimal, targeted changes — a single library patched, a bounded fix — and the cost of not applying them is severe. Feature updates carry more risk and less urgency: a major version bump can change defaults, drop options, or alter behaviour your setup quietly depends on. A sensible default is therefore to automate security updates aggressively while keeping a human in the loop for larger version jumps. That is precisely the line unattended-upgrades lets you draw, and drawing it well is the whole art of configuring the tool.

Patching sits alongside the other unglamorous defences that actually keep a box standing — locking down remote access, as in my SSH hardening guide, and turning away the automated probes with tools like fail2ban and CrowdSec. None of those help if the underlying package has a known hole in it; patching is the layer that closes the hole itself.

Installing and enabling on Debian/Ubuntu

Advertisement

The package is usually a command or two away:

1
2
sudo apt update
sudo apt install unattended-upgrades apt-listchanges

apt-listchanges summarises what changed in each upgrade, which is handy in the notification emails. Now enable the automated runs:

1
sudo dpkg-reconfigure --priority=low unattended-upgrades

This writes /etc/apt/apt.conf.d/20auto-upgrades, which controls the schedule cadence:

1
2
3
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";

The 1 values mean “do this daily” (the unit is days; set Update-Package-Lists "2" and it refreshes every second day, and so on). With these in place, a systemd timer — apt-daily.timer and apt-daily-upgrade.timer — handles the actual runs, with a randomised delay so the whole fleet doesn’t hammer the mirrors at the same instant. You can inspect exactly when the next run is scheduled with systemctl list-timers apt-daily*, which is the first thing to check if you are ever unsure whether automation is actually armed.

Limiting to security updates only

The behaviour you really care about lives in /etc/apt/apt.conf.d/50unattended-upgrades. This is the single most important file in the whole setup, and it rewards a careful read. Open it and look at the Allowed-Origins (Ubuntu) or Origins-Pattern (Debian) block. To restrict automatic upgrades to security only, keep the security line enabled and comment out the rest. On Ubuntu:

1
2
3
4
5
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
//  "${distro_id}:${distro_codename}-updates";
//  "${distro_id}:${distro_codename}-backports";
};

On Debian the equivalent stanza references origin=Debian,codename=${distro_codename}-security,label=Debian-Security. The double-slash // is the comment marker in APT’s configuration syntax, not a URL — a detail that trips people up the first time they edit these files. With only the security origin active, you get the urgent fixes automatically while leaving general updates for a human to review and apply on their own schedule. This single decision — security-only versus everything — is the one that separates “automation I trust” from “automation that eventually breaks something at 4am and teaches me never to trust automation again”.

While you are in this file, you can also tell it never to touch certain packages — useful for a database or anything you upgrade by hand:

1
2
3
4
Unattended-Upgrade::Package-Blacklist {
    "postgresql";
    "linux-image-";
};

A few other knobs in the same file are worth knowing. Unattended-Upgrade::Remove-Unused-Dependencies "true"; cleans up orphaned packages so disk usage doesn’t creep, and Unattended-Upgrade::MinimalSteps "true"; breaks the upgrade into smaller transactions so that, if the run is interrupted, you are left in a recoverable state rather than half-way through a single giant operation. Both are sensible defaults for an unattended context, where nobody is watching to clean up after a partial failure.

Reboots: when and whether to allow them

Some updates, the kernel especially, only take full effect after a reboot. unattended-upgrades can reboot for you, but this is the setting that demands the most thought. In the same 50unattended-upgrades file:

1
2
3
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

This reboots only when needed, at 04:00, and -WithUsers "false" holds off if someone is logged in. For a single host an automatic reboot in a quiet window is often the right call — an unpatched kernel waiting indefinitely for a manual reboot is a half-applied fix. For a cluster, never let every node reboot at the same moment; either stagger the reboot times or, better, leave Automatic-Reboot "false" and orchestrate reboots through your own rolling process so you never lose quorum. Consider needrestart or live-patching where a reboot is genuinely unacceptable.

Email notifications and logs

You want to know what happened, especially when something fails. Configure an address:

1
2
Unattended-Upgrade::Mail "[email protected]";
Unattended-Upgrade::MailReport "on-change";

on-change emails you whenever packages are upgraded or an error occurs, rather than every single day — signal over noise. This requires a working local mail setup (a relay such as msmtp or a configured MTA).

For after-the-fact inspection, the logs live in /var/log/unattended-upgrades/, and the systemd journal records the timer runs:

1
2
sudo cat /var/log/unattended-upgrades/unattended-upgrades.log
journalctl -u apt-daily-upgrade.service --since "yesterday"

The Fedora and RHEL equivalent

If you are on a Red Hat-family distribution, the same job is done by dnf-automatic:

1
sudo dnf install dnf-automatic

Edit /etc/dnf/automatic.conf to choose what it does. The key settings are upgrade_type = security to restrict to security updates, apply_updates = yes to actually install rather than merely download, and an [emitters] section for notifications. Then enable the timer:

1
sudo systemctl enable --now dnf-automatic-install.timer

The philosophy is identical: automate the urgent, low-risk security fixes and keep humans for the rest.

Testing before you trust it

Do not enable automation blindly and walk away. Run a dry run to see what would be upgraded:

1
sudo unattended-upgrades --dry-run --debug

This prints the candidate packages and the origins it considers, so you can confirm your Allowed-Origins filtering does what you expect. Read the output carefully: if it proposes upgrading packages you meant to hold back, your origin filter is wrong and you have just caught it before it did any harm. Watch the first few real cycles, read the notification emails, and check the logs. Only once you have seen it behave sensibly should you let it run truly unattended.

Troubleshooting: when the automation misbehaves

Automation that runs while you sleep needs a diagnostic routine for the morning after, because the failure modes are specific and mostly cheap to fix once you know them.

Nothing is being upgraded at all. Check the timer is enabled with systemctl status apt-daily-upgrade.timer, and confirm APT::Periodic::Unattended-Upgrade "1"; is actually set — a dpkg-reconfigure that was answered “no” leaves it at 0. Then run the dry run above; if it lists candidates but the timer never applies them, the timer is the problem, not the config.

It upgraded something you did not want touched. Your Allowed-Origins is too permissive or your Package-Blacklist entry did not match. Blacklist entries are matched as regular expressions against package names, so linux-image- catches the whole kernel-image family. Verify with the dry run rather than by waiting for the next nightly cycle.

A run failed halfway and left dpkg in a broken state. This is the classic partial-upgrade mess. sudo dpkg --configure -a finishes any interrupted configuration, and sudo apt --fix-broken install resolves dependency breakage. Setting MinimalSteps "true" (which you already have) makes this far less likely, because the work is committed in small transactions rather than one giant one.

Emails never arrive. unattended-upgrades does not include a mail server; it hands the message to the local MTA. If you have no relay configured, install and set up something like msmtp first, then test with echo test | mail -s test [email protected]. No working MTA means silent failures, which defeats the entire point of notifications.

A held-back kernel means reboots pile up. If Automatic-Reboot is false (correct for clusters), check for a pending reboot with [ -f /var/run/reboot-required ] && cat /var/run/reboot-required.pkgs. A running kernel that no longer receives the fixes you have installed on disk is a half-applied patch — schedule the reboot deliberately rather than letting it drift.

Cautions for production

Automation is a tool, not a religion, and a few caveats keep it from biting you. Stage updates where you can — let a non-critical or pre-production host take the patches first, then promote to production once you are confident. Pair the automation with monitoring so that if a patch does break a service, you find out in minutes rather than from a customer; a self-hosted uptime checker that alerts you the moment a service stops responding is the natural companion to unattended patching. For fleets, drive patching through configuration management or a rolling orchestrator so reboots are staggered and capacity is preserved. And always keep a tested backup, because the safety net that lets you patch boldly is the ability to roll back without panic. Automation that you cannot undo is not a convenience; it is a liability with a timer attached.

Is it worth it, and who is it for

For any single machine you care about but do not babysit — a home server, a personal VPS, a small fleet without a dedicated ops team — automating security patches is close to a no-brainer. The realistic alternative is not “careful manual patching on a disciplined schedule”; it is “patching whenever you happen to remember”, which in practice means falling weeks behind on exactly the fixes that matter most. Security-only automation closes that gap for the price of an afternoon’s careful configuration.

Where I would hold back is the fully hands-off, auto-rebooting setup on anything with strict uptime requirements or fragile, tightly coupled services. There, automate the download and install of security patches but orchestrate the reboots yourself, and stage everything through a pre-production host first. And if you run a large fleet, the individual-host unattended-upgrades approach is a starting point, not the destination — graduate to configuration management so the policy lives in version control rather than scattered across fifty 50unattended-upgrades files.

Unpatched, known vulnerabilities are the bread and butter of opportunistic attackers, and the only reliable defence is to remove the human procrastination from the loop. unattended-upgrades on Debian and Ubuntu — and dnf-automatic on Fedora and RHEL — lets you apply security fixes automatically, restrict to the low-risk updates that matter most, control reboot windows, and get notified when anything changes. Configure it thoughtfully, test it before you trust it, back it with monitoring and backups, and let your servers patch themselves while you sleep.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.