Automatic Security Updates on a Linux VPS: unattended-upgrades Without Breaking Production
Set up automatic security patching on a Debian, Ubuntu or Alma VPS: unattended-upgrades config, reboot windows, needrestart, package holds and honest limits.
Most VPS compromises don't start with a clever zero-day. They start with a server that hasn't installed a security update in four months, running a web stack with a publicly known CVE. Unattended patching is the single cheapest risk reduction available to anyone running their own server — and it's also the thing people avoid, because a bad automatic upgrade at 3 a.m. can take a site down.
This guide shows how to configure automatic security updates properly on a Linux VPS: what to automate, what to leave manual, how to handle reboots and service restarts, and where automation genuinely can't help you.
The short answer
On a Debian or Ubuntu VPS, install and enable unattended-upgrades, restrict it to the security repositories, enable automatic reboots inside a maintenance window, and install needrestart so patched services actually restart. On AlmaLinux, Rocky or RHEL, use dnf-automatic with upgrade_type = security and apply_updates = yes.
Then do the part nobody does: verify it's working a week later, and make sure you have a tested backup and a way back in if a reboot goes wrong.
Why this matters more on a privacy-focused server
If you're running a VPS specifically to keep control of your own data — a mail server, a VPN endpoint, file sync, a monitoring stack — the server is your privacy boundary. An unpatched service that gets exploited doesn't just cost you uptime; it hands an attacker the plaintext of everything the box touches.
This is also where people misjudge what a no-KYC or offshore setup does for them. Paying anonymously in crypto and hosting in Romania changes what your provider and third parties know about you. It does nothing about a remote code execution bug in your reverse proxy. Jurisdiction is not a patch level. If a server you run is breached, the exposure is on your side of the line, and the first thing an intruder does is look for credentials, wallets, SSH keys and config files.
Debian and Ubuntu: unattended-upgrades
Install the package and the periodic-task config:
sudo apt update
sudo apt install unattended-upgrades apt-listchanges needrestart
Enable the periodic timers
The scheduling lives in /etc/apt/apt.conf.d/20auto-upgrades:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
These are driven by two systemd timers — apt-daily.timer (metadata refresh and downloads) and apt-daily-upgrade.timer (the actual install). Check them:
systemctl list-timers 'apt-daily*'
Both have a randomized delay so that thousands of machines don't hammer mirrors at the same second. If you want the install to land in a specific window, override the timer rather than fighting the config:
sudo systemctl edit apt-daily-upgrade.timer
[Timer]
OnCalendar=
OnCalendar=*-*-* 03:30
RandomizedDelaySec=30m
Restrict it to security updates
The important choices are in /etc/apt/apt.conf.d/50unattended-upgrades. On Debian, keep only the security origin uncommented:
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};
On Ubuntu, the equivalent allowed-origins entry is:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Why security-only? Because -updates on Ubuntu and stable point updates on Debian can include behaviour changes, and a package that changes a default config at 3 a.m. is exactly the failure mode that makes people disable automation entirely. Security-only patching is where the risk/benefit ratio is best. Do feature and version upgrades by hand, when you're awake and can watch logs.
Reboots, services and the parts that break
A patched library doesn't protect anything until every process using it restarts. A patched kernel does nothing until you reboot. Three settings handle this:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Automatic-Reboot-WithUsers "false" skips the reboot if someone is logged in — useful if you don't want to be kicked out mid-session, but it also means a forgotten tmux session can silently block reboots for weeks. Decide which failure you prefer and check /var/run/reboot-required occasionally either way:
[ -f /var/run/reboot-required ] && cat /var/run/reboot-required.pkgs
For userspace services, needrestart detects processes still mapping deleted libraries. It runs interactively by default on some distributions, which is useless in an unattended context. Set it to act on its own in /etc/needrestart/needrestart.conf:
$nrconf{restart} = 'a';
Add anything you explicitly do not want auto-restarted to $nrconf{blacklist} — database engines are the usual candidates, since a surprise restart under load is worse than waiting for your maintenance window.
Before you enable automatic reboots, check one thing: does your server come back unattended? If you use full-disk encryption, an automatic reboot will park the machine at a passphrase prompt until you unlock it remotely, which is a very unpleasant surprise at 4 a.m. Read what LUKS actually protects on a VPS before combining encryption with unattended reboots, and test a manual reboot first.
Notifications and holds
Silent automation is how you end up trusting something that stopped working months ago. If the server can send mail, enable reports:
Unattended-Upgrade::Mail "[email protected]";
Unattended-Upgrade::MailReport "on-change";
If it can't send mail — which is common, since many providers restrict port 25 — rely on logs and monitoring instead (covered below).
To exclude specific packages, either use the blacklist:
Unattended-Upgrade::Package-Blacklist {
"mariadb-server";
"nginx";
};
…or pin them with APT itself:
sudo apt-mark hold mariadb-server
apt-mark showhold
Use holds sparingly. A held package that never gets patched manually is worse than an occasional restart.
Test it before you trust it
sudo unattended-upgrade --dry-run --debug
Read the output. It tells you which origins it considers allowed, which packages it would install, and which it skips. Then, a few days after enabling, check the real log:
sudo tail -n 50 /var/log/unattended-upgrades/unattended-upgrades.log
journalctl -u unattended-upgrades --since '-7 days'
AlmaLinux, Rocky and RHEL: dnf-automatic
sudo dnf install dnf-automatic
Edit /etc/dnf/automatic.conf:
[commands]
upgrade_type = security
random_sleep = 1800
download_updates = yes
apply_updates = yes
reboot = when-needed
[emitters]
emit_via = stdio
Then enable the timer that actually installs:
sudo systemctl enable --now dnf-automatic-install.timer
Security-only filtering depends on the repositories shipping errata metadata. Verify yours do before relying on it:
dnf updateinfo list security
If that returns nothing on a machine that clearly has pending updates, your repos aren't publishing advisories and upgrade_type = security will effectively install nothing. In that case either switch to default and accept broader updates, or handle patching manually.
For restarting affected services, dnf needs-restarting -r tells you whether a reboot is required, and dnf needs-restarting -s lists services running outdated code.
What automation does not cover
This is where a lot of servers get owned despite "having automatic updates."
Containers. Your distro's package manager does not patch anything inside a Docker image. A container built on debian:bookworm six months ago still has six-month-old libraries no matter how current the host is. You need to rebuild or re-pull images on a schedule and restart the containers. Automatic image updaters exist, but pulling a floating tag unattended means running code you haven't reviewed; pinning digests and updating deliberately is safer. If you run containers, also review how Docker publishes ports around your firewall — an unpatched service exposed to the internet by accident is the worst combination.
Application dependencies. Packages installed with pip, npm, composer, cargo or go install are invisible to apt and dnf. Same for anything compiled from source into /usr/local. These need their own update process.
Third-party repositories and vendor scripts. External repos may or may not be covered by your origins pattern. Check explicitly rather than assuming; an unlisted origin is silently ignored.
Web application code. WordPress, Nextcloud, GitLab and similar apps have their own update channels. In practice, outdated PHP applications and their plugins are a far more common entry point than an outdated kernel.
Configuration drift. Automation installs packages. It does not notice that you opened a debug port, left a default credential in place, or exposed an admin panel.
Verify, don't assume
Two checks turn this from hopeful into reliable.
First, surface patch state in your monitoring. A simple textfile-collector script that exports the number of pending security updates and whether a reboot is required is enough to alert you when a server falls behind. If you don't already have something in place, a self-hosted monitoring stack without third-party agents fits this well and keeps the telemetry on your own infrastructure.
Second, make sure a failed upgrade or reboot is survivable. That means encrypted off-site backups with a restore you've actually tested, plus a known-good route back into the machine — provider console access, or a second key and a documented recovery procedure if your firewall or SSH config is what broke.
Common mistakes
- Enabling
-updatesor full upgrades unattended on a production box. Security-only is the sweet spot. - Automating installs but never rebooting. Kernel, glibc and OpenSSL fixes need a restart to take effect. A server with 200 days of uptime and current packages is not fully patched.
- Blacklisting a service "temporarily" and forgetting for a year.
- Assuming a package was installed because the timer ran. Timers refresh metadata; only the upgrade unit installs. Read the log.
- Running
apt full-upgradeunattended across a major release. Distribution upgrades are a manual, snapshot-first job. - Treating patching as the whole security story. It pairs with a restrictive firewall, key-only SSH, and minimal exposed surface — not a substitute for them.
Honest trade-offs
Automatic security updates trade a small, bounded risk of unplanned breakage for a large reduction in exposure to publicly known vulnerabilities. For nearly every single-server setup, that's a good trade. Attackers scan for known CVEs continuously and at scale; they do not wait for your maintenance window.
The situations where full automation is genuinely wrong are narrow: a service where a 60-second restart is unacceptable and you have no redundancy, or a heavily customized stack where package updates reliably conflict with local changes. In those cases the answer is download_updates = yes / apply_updates = no, plus a real weekly review — not "we'll get to it."
One more trade-off worth naming for privacy-minded readers: some vendor patch services require an account tied to an identity. Ubuntu's kernel livepatching, for example, is available at no cost for a limited number of machines but requires a vendor account and token on the server. It only covers high and critical kernel CVEs, and you still need to reboot eventually. If you're deliberately running an anonymous, crypto-paid VPS, attaching a vendor identity to it may not be a trade you want. Scheduled reboots in a maintenance window achieve the same goal with no third-party account.
FAQ
Will automatic updates restart my web server without warning?
Only if something it depends on was patched and needrestart is configured to act. That restart is usually sub-second for nginx or PHP-FPM. Blacklist databases if you want tighter control.
Do I still need to log in and run apt upgrade?
Yes, periodically. Security-only automation deliberately leaves non-security updates, distribution upgrades and held packages to you.
Is unattended patching safe on a low-RAM VPS? Generally yes, but package installs and service restarts do spike memory use. On a 512MB–1GB instance, make sure you have swap or zram configured so an upgrade doesn't trigger the OOM killer.
How do I know whether patching is actually working?
unattended-upgrades.log, journalctl, the presence or absence of `/var/run/reboot
