SYSTEMS OPERATIONAL
VPS

Automatic Security Updates on a VPS: Setting It Up Safely

Getwebup 5 min read

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.

Frequently asked questions

Will unattended-upgrades update my kernel automatically?

Yes, if a kernel security patch lands in the security repo, it gets downloaded and installed like any other package - but the running kernel only changes after a reboot. With Automatic-Reboot left off, the server keeps running the old kernel in memory until you reboot it yourself; the /var/run/reboot-required flag tells you it's waiting.

Is this safe to run on a cPanel or WHM VPS?

Yes, as long as you blacklist the packages cPanel manages itself, like Apache, MySQL/MariaDB, and PHP builds installed through EasyApache. cPanel expects to control those versions through WHM, and having apt patch them independently can cause version mismatches. Stick to OS-level security packages (openssl, sudo, glibc, kernel) and leave the hosting stack to WHM's own update settings.

How do I know if unattended-upgrades is actually running?

Check systemctl status apt-daily-upgrade.timer and apt-daily.timer - both should show as active. Then look at /var/log/unattended-upgrades/unattended-upgrades.log for recent entries. If the log is empty or stale, the timers likely aren't firing; systemctl list-timers will show you the next scheduled run.

What's the difference between this and just running apt upgrade in a cron job?

A plain cron job with apt upgrade -y pulls in every available update, including non-security bug fixes and anything not on a blacklist - which is how you end up with an unplanned MySQL or PHP version bump. unattended-upgrades scopes itself to a defined origin (security-only by default), respects a package blacklist, and logs its decisions, so it's a controlled patch process rather than a blind one.

My reboot-required flag never clears even after rebooting. What's wrong?

That usually means a second update landed between your reboot and the flag check, or a package left a stale marker in /var/lib/update-notifier/. Manually remove /var/run/reboot-required and /var/run/reboot-required.pkgs after confirming uname -r matches the newest installed kernel, then let the next patch cycle recreate the flag only if it's genuinely needed again.

#vps #unattended-upgrades #security-updates #ubuntu #linux #server-maintenance

Keep reading

Chat with Support