Automatic Security Updates on a Debian or Ubuntu VPS: unattended-upgrades Done Right
Configure unattended-upgrades safely on a VPS: security-only origins, service restarts with needrestart, kernel reboot windows, third-party repos and mistakes.
Unpatched packages are one of the most boring and most common reasons a VPS gets compromised. Nobody breaks into a well-configured server through a zero-day; they scan the internet for an old Exim, an outdated Confluence, or a libssl version with a known remote bug. Automating security updates removes that entire class of risk with maybe twenty minutes of setup — but only if you configure it so it actually applies the patches, restarts the affected services, and reboots when the kernel changes.
This guide covers a production-sane unattended-upgrades setup on Debian and Ubuntu, including the parts most tutorials skip: service restarts, reboot windows, third-party repositories, config-file prompts, and how to verify it's really working.
Direct answer
On a single-purpose Debian or Ubuntu VPS, enable unattended-upgrades restricted to the security pocket only, add needrestart in automatic mode so services relink against patched libraries, and schedule automatic reboots in a low-traffic window (or monitor /var/run/reboot-required and reboot manually).
Minimal working setup:
apt update
apt install unattended-upgrades needrestart
dpkg-reconfigure -plow unattended-upgrades # answers "yes" -> writes 20auto-upgrades
Then verify with a dry run:
unattended-upgrade --dry-run --debug
Everything below is about making that setup behave predictably instead of surprising you at 4 a.m.
How Debian and Ubuntu security updates actually work
unattended-upgrades does not decide what is "a security update" on its own. It matches package candidates against a list of allowed origins, which come from your APT sources.
- Debian: security patches ship from the
Debian-Securitylabel (bookworm-security). Regular point-release fixes come from the main suite. - Ubuntu: security patches ship in the
-securitypocket (e.g.noble-security);-updatescarries broader bug fixes and version bumps. Ubuntu also has ESM/Ubuntu Pro origins for packages inuniverseand for EOL releases.
This distinction matters. Allowing only the security origin gives you a narrow, well-tested stream of changes. Allowing -updates on Ubuntu pulls in more churn — often fine, occasionally a behaviour change you didn't ask for.
Check what your system sees:
apt-cache policy
apt-cache policy nginx
The o=, l=, n= and a= fields shown there are exactly what you put in the allowlist.
Configuring 50unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades. A conservative configuration for Debian:
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};
Unattended-Upgrade::Package-Blocklist {
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
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::Automatic-Reboot-WithUsers "false";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
Unattended-Upgrade::SyslogEnable "true";
On Ubuntu the allowlist entries use the ${distro_id} form instead:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
A few notes on the flags:
Remove-Unused-Kernel-Packagesis important on VPS instances with a small/boot. Without it, three or four kernels accumulate and the next kernel upgrade fails mid-dpkgbecause/bootis full. That failure mode is annoyingly common on 512 MB–1 GB plans with tiny boot partitions.Remove-Unused-Dependencies "false"is deliberate. Full autoremove during an unattended run has, historically, surprised people by removing packages they still wanted. Runapt autoremovemanually when you're watching.MinimalStepssplits the upgrade into smallerdpkgtransactions so an interrupted run leaves less breakage.
And in /etc/apt/apt.conf.d/20auto-upgrades:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
The work is triggered by apt-daily.timer (list refresh) and apt-daily-upgrade.timer (the upgrade run), both with randomized delays so mirrors don't get hammered. Confirm they're active:
systemctl list-timers 'apt-daily*'
If you want a tighter window, override the timer rather than editing the shipped unit:
systemctl edit apt-daily-upgrade.timer
The part everyone forgets: restarting services
Installing a patched libssl3 does nothing for a running nginx or Postfix process — it keeps the old library mapped in memory until it restarts. A server can be fully "patched" according to apt and still vulnerable for weeks.
needrestart solves this. It scans running processes for deleted mappings and restarts the affected systemd services. Set it to non-interactive mode in /etc/needrestart/needrestart.conf:
$nrconf{restart} = 'a';
$nrconf{kernelhints} = 0;
Mode a means "restart services automatically without asking". Without this, needrestart opens an interactive dialog during apt runs, which on some Ubuntu systems is exactly why an automated upgrade appears to hang.
If a service must never be restarted automatically (a long-running database with a slow start, a stateful queue worker), blacklist it explicitly:
$nrconf{blacklist_rc} = [ '^mariadb$' ];
Then restart it yourself during a maintenance window. That's a conscious trade-off, not an oversight.
To check what's stale at any time:
needrestart -v -r l # list only, no restarts
Kernel updates and reboots
Library restarts don't cover the kernel. After a kernel package upgrade, Debian and Ubuntu create /var/run/reboot-required, and you keep running the old kernel until you reboot.
You have three honest options:
Automatic-Reboot "true"with a fixed time. Simplest and safest for stateless workloads: web servers behind a proxy, static sites, Tor relays, mail servers you can afford to have offline for 30 seconds. SetAutomatic-Reboot-Timeto a genuinely quiet hour in your users' timezone, not the server's default.- Monitor and reboot manually. Check
/var/run/reboot-requiredfrom your monitoring or a login banner and reboot when convenient. This is right for databases and anything where an unexpected restart costs more than a few minutes of stale kernel. - Livepatching. Ubuntu offers kernel livepatching through Ubuntu Pro, which includes a free personal tier; it applies critical kernel fixes without reboot. It covers high-severity kernel CVEs, not userspace, and does not remove the need for eventual reboots.
Whichever you pick, know your kernel state:
uname -r
dpkg -l 'linux-image*' | grep ^ii
If the newest installed image doesn't match uname -r, you owe the machine a reboot. And make sure your VPS actually boots cleanly unattended — test a reboot once, deliberately, before you trust an automated one. If your setup involves encrypted volumes or a remote unlock step, an automatic 03:30 reboot leaves the server sitting at a passphrase prompt; see the trade-offs in what LUKS really protects on a remote server.
Third-party repositories are the real gap
Most compromises I see discussed publicly don't involve stale distro packages — they involve stale application stacks installed from vendor repos, upstream tarballs, or pip/npm. unattended-upgrades only touches origins you explicitly allow, so a Docker CE, PGDG, NodeSource or Nginx-mainline repo is ignored by default.
Decide per repository:
apt-cache policy docker-ce
# note the "o=" origin and "a=" suite, e.g. o=Docker,a=bookworm
Then add a matching pattern:
Unattended-Upgrade::Origins-Pattern {
"origin=Docker";
};
Be aware you're now accepting whatever the vendor pushes, including minor version jumps. For fast-moving components (databases, container runtimes) many admins prefer to leave them manual and patch them deliberately.
The bigger blind spots are things apt never sees at all:
- Container images — patching the host does nothing for a two-year-old base image. Rebuild and redeploy on a schedule.
- Language-level dependencies —
pip,npm,composer, Go modules. - Web applications installed by unzipping a release (WordPress plugins are the classic).
- Anything compiled from source into
/usr/local.
Write these down. An update policy that covers only apt while a vulnerable PHP app sits in /var/www is theatre.
Notifications without running a mail server
unattended-upgrades can email reports:
Unattended-Upgrade::Mail "root";
Unattended-Upgrade::MailReport "on-change";
That needs a working MTA, which is a whole project of its own — see the reality check in running a mail server on a VPS. If you'd rather not, read the logs directly:
less /var/log/unattended-upgrades/unattended-upgrades.log
journalctl -u unattended-upgrades.service --since -7d
grep -h ' upgrade ' /var/log/dpkg.log*
A five-line script that checks for /var/run/reboot-required and the last successful run, exposed to your monitoring, is more useful than email you'll filter away. If you deliberately keep logging minimal for privacy reasons, keep in mind that a volatile journal loses upgrade history on reboot — plan the retention trade-off consciously, as discussed in VPS log minimization.
Common mistakes
Enabling the timers but never checking the log. Silent failures are normal: a held package, a full /boot, a broken third-party repo, an interrupted dpkg. Automation you don't verify is not automation.
Config-file prompts stalling the run. When a package ships a changed conffile you've edited, dpkg wants to ask. Unattended runs can't answer. Most setups add:
Dpkg::Options {
"--force-confdef";
"--force-confold";
};
This keeps your version and prevents hangs — but it also means you silently miss new upstream defaults. Periodically hunt for leftovers:
find /etc -name '*.dpkg-dist' -o -name '*.ucf-dist' -o -name '*.dpkg-new'
Assuming automatic updates replace backups. A bad upgrade, or an autoremove that takes out something load-bearing, is exactly the situation where a tested restore matters. Get encrypted off-site backups with restic or Borg working first, and take a snapshot before major changes.
Forgetting the distro release itself. unattended-upgrades will not do a release upgrade. When Debian oldstable or an Ubuntu LTS reaches end of standard support, security patches stop and no amount of automation helps. Put the EOL date in your calendar and plan the upgrade or a rebuild on a fresh instance.
Treating patching as the whole security posture. It's one layer. It doesn't compensate for a password-authenticated SSH port or an open database. Pair it with a restrictive nftables baseline and keys-only SSH.
Pros and cons
In favour: removes the largest realistic attack surface — known, published CVEs in internet-facing services; closes the window between patch release and application from weeks to hours; costs almost nothing to maintain once configured.
Against: unattended changes can break behaviour, especially if you allow -updates or vendor repos; automatic restarts and reboots cause brief downtime; a failed transaction on an unmonitored box can leave dpkg in a half-configured state.
For a single-service VPS the trade-off is not close: enable it. For a tightly coupled production cluster, restrict to the security pocket, blacklist stateful services from auto-restart, and reboot on your own schedule.
FAQ
Will it reboot my server without warning?
Only if Automatic-Reboot is "true". Default is false, in which case you'll accumulate a pending reboot flag until you act.
Debian security-only, or Ubuntu -updates too?
Security-only is the conservative default and what I'd recommend for servers hosting other people's traffic. Adding -updates on Ubuntu gets you bug fixes sooner at the cost of more change.
What about AlmaLinux, Rocky or Fedora?
Use dnf-automatic with upgrade_type = security in /etc/dnf/automatic.conf, enable dnf-automatic-install.timer, and add dnf-utils + needs-restarting -r for reboot detection. Same principles, different tooling.
Does this help if I'm already compromised? No. Patching prevents entry; it does nothing about an attacker already inside, and an updated package can happily coexist with a persistent backdoor. If you suspect that, work through detecting and rebuilding a compromised server instead.
Do I need it on a VPS that only runs Docker? Yes — the host kernel, SSH, and the container runtime all come from the host. But it won't patch anything inside your images; that's a separate rebuild pipeline.
Conclusion
Configure unattended-upgrades for the security origin, install needrestart in automatic mode, enable kernel package cleanup so /boot never fills, decide explicitly how reboots happen, and check the log occasionally. That combination handles the unglamorous majority of server compromises.
If you're spinning up a new server — including full-root KVM VPS instances in Romania paid with Monero, Bitcoin or Litecoin at IronBalkans — do this in the first ten minutes, alongside SSH keys and your firewall. It's the cheapest security work you'll ever do, and unlike most hardening, it keeps paying off while you're not looking.
