Automatic Security Updates on a VPS: Setting It Up Safely

· 5 min read · 1 view · Getwebup

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 --upgradable shows 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-listchanges

2. 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: compare dpkg -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 --debug

This 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

SettingFileWhat It Controls
APT::Periodic::Unattended-Upgrade20auto-upgradesTurns the daily run on/off
Allowed-Origins50unattended-upgradesWhich repos it's allowed to pull from (security vs. all updates)
Package-Blacklist50unattended-upgradesPackages it must never touch automatically
Automatic-Reboot50unattended-upgradesWhether a needed reboot happens on its own
Automatic-Reboot-Time50unattended-upgradesWhat time that reboot happens
Mail / MailReport50unattended-upgradesWho 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-upgrade across 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.log is 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-Blacklist immediately - 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.

Questions people actually ask