Back to news
· 11 min read

Automatic Security Updates on a VPS — unattended-upgrades and dnf-automatic

Make your VPS install security updates by itself: unattended-upgrades on Ubuntu and Debian, dnf-automatic on AlmaLinux, safe reboot settings and how to verify it works.

Automatic Security Updates on a VPS — unattended-upgrades and dnf-automatic

Automatic Security Updates on a VPS — unattended-upgrades and dnf-automatic

Most servers that get broken into are not attacked with anything clever. They run a package with a known, already-fixed vulnerability, because nobody logged in to run apt upgrade for three months. A VPS that patches itself every night closes most of that window without you having to remember anything.

This guide shows how to set up automatic security updates on Ubuntu, Debian and AlmaLinux, how to decide what gets updated and when the server reboots, and — most importantly — how to check that it is actually working, instead of assuming it is.

What automatic updates should (and should not) do

The goal is not "install everything, always". A sensible setup on a production VPS looks like this:

  • Security updates are installed automatically, every day, from the official distribution repositories.
  • Normal feature and bug-fix updates are either left for you to install manually, or included only if the server runs nothing fragile.
  • Packages you do not want touched (for example the database server) can be excluded.
  • Reboots happen at a time you choose, or never automatically — but you are told when one is needed.
  • Every run is logged, so you can see what changed when something behaves differently.

Security updates from Ubuntu, Debian and AlmaLinux are deliberately small: they backport the fix to the version you already run instead of jumping to a new release. That is why installing them automatically is low-risk, and why it is the default on Ubuntu Server.

Ubuntu and Debian — install and enable unattended-upgrades

On Ubuntu Server the unattended-upgrades package is usually already installed and enabled. On Debian, and on minimal images, install it yourself:

sudo apt update
sudo apt install -y unattended-upgrades apt-listchanges

# Turn on the daily package list refresh and the daily unattended upgrade
sudo dpkg-reconfigure -plow unattended-upgrades   # answer "Yes"

The dpkg-reconfigure step writes /etc/apt/apt.conf.d/20auto-upgrades. Check that it contains these two lines (create the file with this content if it does not exist):

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

The "1" means "every 1 day". Confirm that APT sees the setting and that the systemd timers which trigger it are active:

apt-config dump APT::Periodic::Unattended-Upgrade
systemctl list-timers 'apt-daily*'

You should see apt-daily.timer (refreshes package lists) and apt-daily-upgrade.timer (installs the updates), each with a "NEXT" time in the coming hours.

Choose what gets updated — the Allowed-Origins list

The main configuration file is /etc/apt/apt.conf.d/50unattended-upgrades. Its most important block decides which repositories updates may come from. On Ubuntu it looks like this:

Unattended-Upgrade::Allowed-Origins {
        "${distro_id}:${distro_codename}";
        "${distro_id}:${distro_codename}-security";
        "${distro_id}ESMApps:${distro_codename}-apps-security";
        "${distro_id}ESM:${distro_codename}-infra-security";
//      "${distro_id}:${distro_codename}-updates";
};

Lines starting with // are comments. The -security lines are the important ones. If you also uncomment -updates, the server will install regular bug-fix updates too — fine for a simple web server, but leave it off if you prefer to review those yourself.

Debian uses a pattern-based block called Origins-Pattern instead. The defaults include the security repository and the point-release updates of your stable version:

Unattended-Upgrade::Origins-Pattern {
        "origin=Debian,codename=${distro_codename},label=Debian";
        "origin=Debian,codename=${distro_codename},label=Debian-Security";
        "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};

Third-party repositories such as nginx.org, Docker or a PHP repository are not updated unless you add them. To find the exact origin and codename values of a repository, look at the o=, n= and l= fields here:

apt-cache policy | grep -E 'release|http' | head -40

Then add a matching line, for example "origin=nginx,codename=${distro_codename}"; inside Origins-Pattern (this block works on Ubuntu as well). Only do this for repositories you trust to ship safe updates.

Exclude packages you want to upgrade by hand

Some upgrades are better done while you are watching: a database server that restarts and replays its logs, or an application that pins a specific library version. List them in the blacklist in the same file. Entries are regular expressions matched against package names:

Unattended-Upgrade::Package-Blacklist {
        "mariadb-";
        "mysql-server";
        "postgresql-";
};

Keep this list short. Every package on it is a package that will not receive security fixes until you remember to update it. A reasonable rule: exclude only services where an unexpected restart would hurt, and put a monthly reminder in your calendar to update them manually.

A few other options in the same file are worth enabling on almost every VPS:

// Remove old kernels and dependencies that are no longer needed
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";

// Finish safely if the run is interrupted (e.g. by a shutdown)
Unattended-Upgrade::MinimalSteps "true";

Removing old kernels matters more than it looks: on a small disk, a year of leftover kernels can fill /boot or the root filesystem and make the next update fail.

Reboots — automatic, scheduled or manual

Most updates take effect when the affected service restarts. Kernel updates, and updates to core libraries such as glibc, only fully apply after a reboot. Ubuntu and Debian signal this with a file:

# Exists only when a reboot is needed
cat /var/run/reboot-required
cat /var/run/reboot-required.pkgs   # which packages asked for it

You have three options:

OptionSettingGood for
Never reboot automaticallyAutomatic-Reboot "false"; (default)Servers where you plan every reboot yourself
Reboot at a fixed time when neededAutomatic-Reboot "true"; + Automatic-Reboot-Time "04:00";Single-purpose servers that start everything at boot
Reboot only if nobody is logged inAdd Automatic-Reboot-WithUsers "false";Servers where you sometimes work late over SSH

The settings go into 50unattended-upgrades with the Unattended-Upgrade:: prefix, for example:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";

Before you enable automatic reboots, make sure everything comes back on its own: run systemctl is-enabled nginx mariadb (with your own services), check that Docker containers use restart: unless-stopped, and do one manual sudo reboot to prove it. The time is the server's local time — check it with timedatectl.

For services that need a restart without a reboot, install needrestart (already present on recent Ubuntu releases). After an update, list what is still running old code:

sudo apt install -y needrestart
sudo needrestart -r l      # list only, restart nothing

Control when updates run

By default, apt-daily-upgrade.timer fires around 06:00 with a random delay of up to an hour. If you prefer a quieter time, override the timer instead of editing the packaged file:

sudo systemctl edit apt-daily-upgrade.timer

Add these lines in the editor that opens. The empty OnCalendar= line clears the default before setting a new one:

[Timer]
OnCalendar=
OnCalendar=*-*-* 03:00
RandomizedDelaySec=20m

Apply and verify:

sudo systemctl daemon-reload
systemctl list-timers apt-daily-upgrade.timer

If you enabled automatic reboots, keep the reboot time after the upgrade window, for example updates at 03:00 and reboot at 04:00.

Test it and read the logs

Do not wait until tomorrow to find out whether the configuration is valid. A dry run shows exactly which origins are allowed and which packages would be upgraded, without changing anything:

sudo unattended-upgrade --dry-run --debug 2>&1 | less

Look for the line Allowed origins are: ... and check that it lists the repositories you expect. A syntax error in the config file (a missing semicolon or quote) also shows up here immediately.

After real runs, the history is in three places:

# What unattended-upgrades decided and installed
sudo tail -n 50 /var/log/unattended-upgrades/unattended-upgrades.log

# Full dpkg output of those upgrades
sudo less /var/log/unattended-upgrades/unattended-upgrades-dpkg.log

# Every apt operation, manual or automatic
grep -A3 'Start-Date' /var/log/apt/history.log | tail -n 30

If you want an email after each run, set Unattended-Upgrade::Mail "you@example.com"; and Unattended-Upgrade::MailReport "on-change";. This needs a working local mail command (mailx or sendmail) that can actually deliver mail — which on a VPS is not a given. A simpler and more reliable option is to check the log as part of your regular monitoring; our guide to monitoring a VPS with alerts shows how.

AlmaLinux and Rocky Linux — dnf-automatic

On AlmaLinux 9 and 10 the equivalent tool is dnf-automatic. Install it and open its configuration file:

sudo dnf install -y dnf-automatic
sudo nano /etc/dnf/automatic.conf

The important settings are in the [commands] section:

[commands]
# "security" = only updates marked as security errata; "default" = everything
upgrade_type = security
download_updates = yes
apply_updates = yes
# never | when-changed | when-needed
reboot = never

With apply_updates = no the tool only downloads and reports, which is a good first week while you build trust in it. reboot = when-needed reboots only when an updated package (such as the kernel) requires it. The reboot option exists in the dnf-automatic shipped with current AlmaLinux 9 and 10 releases; on an older install, run sudo dnf upgrade dnf-automatic first.

Enable the timer and confirm it is scheduled:

sudo systemctl enable --now dnf-automatic.timer
systemctl list-timers dnf-automatic.timer

Check what would be installed, whether a reboot is pending, and what the last runs did:

sudo dnf updateinfo list security        # pending security updates
sudo dnf needs-restarting -r             # exit code 1 = reboot needed (dnf-plugins-core)
journalctl -u dnf-automatic.service --since "-7 days"
sudo tail -n 50 /var/log/dnf.log

To exclude packages, add an exclude= line to the [main] section of /etc/dnf/dnf.conf, for example exclude=mariadb-server*. As on Debian, keep the list as short as you can.

What automatic updates do not cover

An updated operating system is only part of the picture. These still need your attention:

  • Docker images. Containers keep running the image they were started from. Pull new images and recreate the containers on a schedule (see our Docker on a VPS guide).
  • Applications you installed yourself — WordPress core and plugins, Composer and npm dependencies, binaries downloaded from GitHub.
  • End-of-life releases. When a distribution version stops receiving security support, there is nothing left to install automatically. Plan the upgrade to the next release before that date.
  • Backups. A rare bad update is easy to survive if you have a recent backup or snapshot of your data.

Check which release you run and when its support ends with cat /etc/os-release and the distribution's release page.

Frequently Asked Questions

Can automatic updates break my server?

It is rare with security-only updates, because they patch the version you already run instead of upgrading to a new one. The more realistic risks are a service that does not restart cleanly and third-party repositories that ship big changes. Limit automatic updates to official security repositories, exclude the few packages you upgrade by hand, and keep backups.

Should I enable automatic reboots?

Enable them if all your services start automatically at boot and a few minutes of downtime at night is acceptable. Otherwise leave them off, but watch for /var/run/reboot-required (Ubuntu, Debian) or dnf needs-restarting -r (AlmaLinux) and reboot yourself within a few days of a kernel update.

How do I know the updates are really being installed?

Run systemctl list-timers to see the next scheduled run, then read /var/log/unattended-upgrades/unattended-upgrades.log or journalctl -u dnf-automatic.service. Running apt list --upgradable or dnf updateinfo list security should show few or no pending security updates on a healthy server.

Does unattended-upgrades update Docker containers or WordPress?

No. It only updates packages installed from the configured APT repositories. Container images, WordPress, plugins and application dependencies must be updated with their own tools.

Do I need to do this on shared web hosting?

No. On shared hosting you do not have root access and do not manage the operating system. What you still update yourself are your applications, such as WordPress and its plugins.


Every Liga Hosting Standard VPS and Performance VPS runs on KVM with full root access, so you choose the distribution, the update policy and the reboot window yourself. If you would rather not manage an operating system at all, our cPanel web hosting plans are the simpler option.