You log into your VPS to deploy something and see "37 packages can be updated, 12 are security updates." You mean to run apt upgrade later, life happens, and three weeks pass. That gap is exactly how boxes get popped through a known, already-patched CVE. Unattended-upgrades closes that gap automatically - if you set it up right. Get it wrong and it'll silently break your PHP-FPM version or reboot your production server at 2 PM on a Tuesday.
Symptom: You're Either Unpatched or Getting Surprise Reboots
This usually shows up as one of two opposite problems:
- Nothing is patched.
apt list --upgradableshows a pile of security fixes going back months. A vulnerability scanner (or your host's abuse team) flags an outdated OpenSSL, sudo, or Exim package. - Something patched itself and broke. A site goes down after a "System restart required" banner turns into an actual reboot nobody scheduled, or a package update silently restarted a service mid-transaction.
Both come from the same root cause: nobody configured automatic updates deliberately, so the server is either doing nothing or doing everything with no guardrails.
Cause: Fresh VPS Images Don't Patch Themselves
A stock Ubuntu or Debian cloud image ships with the unattended-upgrades package already installed but usually not enabled - the config exists in /etc/apt/apt.conf.d/ but the trigger that actually runs it is off. On some minimal images the package isn't even installed. Either way, security patches just sit in apt's cache until a human runs apt upgrade.
The fix isn't "turn on every update" - that's how you end up with an unplanned MySQL major-version bump. It's turning on security updates specifically, keeping everything else manual, and controlling exactly when (or if) the box reboots.
Fix: Set Up Unattended-Upgrades Properly
1. Install the package
sudo apt update
sudo apt install unattended-upgrades apt-listchanges2. Enable the daily trigger
Edit /etc/apt/apt.conf.d/20auto-upgrades (create it if it doesn't exist):
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";This tells apt's daily cron/systemd timer to refresh package lists and run unattended-upgrades once a day.
3. Scope it to security updates only
Open /etc/apt/apt.conf.d/50unattended-upgrades and check the Unattended-Upgrade::Allowed-Origins block. On Ubuntu 22.04/24.04 it looks like this by default:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};Leave the general -updates line commented out unless you specifically want non-security bug fixes applied automatically too - most production servers shouldn't.
4. Blacklist anything you manage by hand
In the same file, uncomment and fill in the package exclusion list so nothing important moves without you:
Unattended-Upgrade::Package-Blacklist {
"mysql-server-*";
"php8.*";
"docker-ce";
};This is the step people skip and then discover their WordPress site is down because PHP jumped a minor version and a compiled extension no longer loads.
5. Turn off automatic reboots, or schedule them deliberately
A kernel or glibc security patch will often need a reboot to fully apply. By default some installs leave this off; make it explicit either way:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";Setting a fixed low-traffic time beats letting the reboot happen whenever the timer fires. If you'd rather handle reboots manually, set Automatic-Reboot "false" and instead alert on the flag file:
test -f /var/run/reboot-required && echo "Reboot needed on $(hostname)"Drop that in a daily cron job that emails you, or feed it into whatever monitoring you already use.
6. Get email notifications
Unattended-Upgrade::Mail "you@yourdomain.com";
Unattended-Upgrade::MailReport "on-change";Make sure the server can actually send outbound mail first - if you've hit Port 25 being blocked on your VPS before, fix that separately or these emails will just vanish.
A quick way to sanity-check this after a patch window: comparedpkg -l | grep -E "openssl|sudo|libssl" output against the CVE advisory that prompted you to look in the first place, so you know the fix actually landed rather than just assuming the cron job ran.Test It Before Trusting It
Don't wait for the 3 AM cron run to find out your config has a syntax error. Run a dry pass with verbose logging:
sudo unattended-upgrades --dry-run --debugThis shows exactly which packages it would touch and why, without installing anything. Check the tail of /var/log/unattended-upgrades/unattended-upgrades.log afterward - it lists every decision the tool made.
Quick Reference: Key Settings
| Setting | File | What It Controls |
|---|---|---|
| APT::Periodic::Unattended-Upgrade | 20auto-upgrades | Turns the daily run on/off |
| Allowed-Origins | 50unattended-upgrades | Which repos it's allowed to pull from (security vs. all updates) |
| Package-Blacklist | 50unattended-upgrades | Packages it must never touch automatically |
| Automatic-Reboot | 50unattended-upgrades | Whether a needed reboot happens on its own |
| Automatic-Reboot-Time | 50unattended-upgrades | What time that reboot happens |
| Mail / MailReport | 50unattended-upgrades | Who gets notified and how often |
Prevention: Treat This as Infrastructure, Not a Set-and-Forget
A few habits keep automatic patching from becoming its own incident:
- Snapshot before major version jumps. Unattended-upgrades sticking to security patches is safe; a manual
apt full-upgradeacross a distro release is not - take a VPS snapshot first regardless of which one you're running. - Watch the log, don't just trust the email.
/var/log/unattended-upgrades/unattended-upgrades.logis the source of truth if a service acts strange after a patch window. - Keep the blacklist current. Every time you manually pin a service version (a specific PHP build, a MySQL version for compatibility), add it to
Package-Blacklistimmediately - don't rely on memory. - Pair it with monitoring, not instead of it. Automatic patching reduces exposure time; it doesn't replace knowing when a service actually goes down. If you don't already have basic uptime/resource alerts on the box, set those up alongside this.
Once this is running, the ticket that used to read "server got compromised through a three-month-old CVE" mostly stops happening - and you stop being the one who has to remember to run apt upgrade every week.