Security

Proactive Patch Management: Automating Security Updates on Linux VPS

Set up unattended-upgrades and dnf-automatic so security patches install themselves, then control reboots, package holds and logging so nothing breaks.

IntermediateLinux server6 min read

Automating security updates on a Linux VPS means letting the package manager install security patches on its own schedule while you keep control of reboots, held packages and logging. On Ubuntu and Debian that tool is unattended-upgrades; on AlmaLinux and Rocky Linux it is dnf-automatic. This guide configures both, adds reboot and kernel handling, and shows you how to prove patches actually landed.

Before you start

You need:

  • SSH access to the server as root or a sudo user. If you are not connected yet, see Connect to your Linux VPS with SSH.
  • A recent backup or provider snapshot. A patch run can restart services or replace a kernel.
  • Knowledge of which distribution you run. Commands below target Ubuntu 22.04/24.04, Debian 12, AlmaLinux 8–9 and Rocky Linux 8–9.
  • A rough maintenance window in mind, for example Sunday 04:00, in the server's timezone.

Warning: Automatic updates can restart services mid-request. If you run a database, mail server or panel, test the update path on a staging VPS before enabling it in production.

Step 1: Record a baseline

Write down what is currently pending so you can tell later whether automation worked.

Bash
cat /etc/os-release
uname -r
uptime

On Debian and Ubuntu:

Bash
sudo apt update
apt list --upgradable

On AlmaLinux and Rocky Linux:

Bash
sudo dnf check-update --security
sudo dnf updateinfo list security

Save the output somewhere outside the server. It is your before-picture.

Step 2: Automate security updates on Ubuntu and Debian

Install the tooling:

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

Enable the daily timers by editing /etc/apt/apt.conf.d/20auto-upgrades:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";

Now edit /etc/apt/apt.conf.d/50unattended-upgrades. Leave the default Allowed-Origins block alone and adjust the behaviour lines:

Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Mail "root";
Unattended-Upgrade::MailReport "on-change";

Automatic-Reboot "false" is deliberate. You will handle reboots yourself in Step 4 so a kernel patch never restarts the box at a random hour.

Test the configuration without changing anything:

Bash
sudo unattended-upgrade --dry-run --debug

The debug output lists every package it would install and every origin it accepts. If the list is empty, your sources are already current.

Step 3: Automate security updates on AlmaLinux and Rocky Linux

Install the timer:

Bash
sudo dnf install dnf-automatic

Edit /etc/dnf/automatic.conf:

INI
[commands]
upgrade_type = security
random_sleep = 360
network_online_timeout = 60
download_updates = yes
apply_updates = yes
reboot = never

[emitters]
emit_via = stdio

upgrade_type = security limits the run to security errata. random_sleep spreads the load so a fleet of VPS instances does not all hit the mirrors at the same second. reboot = never keeps reboots under your control.

Enable and start the timer:

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

Check what the last run did:

Bash
journalctl -u dnf-automatic.service --since "7 days ago"

Step 4: Handle kernel updates and reboots

Security patches to the kernel, glibc or systemd only take effect after a restart. Detect that state instead of guessing.

On Ubuntu and Debian, install needrestart so services using old libraries are flagged:

Bash
sudo apt install needrestart
sudo needrestart -r l

A pending kernel upgrade shows as a file:

Bash
cat /var/run/reboot-required 2>/dev/null || echo "no reboot required"

On AlmaLinux and Rocky Linux:

Bash
sudo dnf needs-restarting -r

Now schedule the reboot inside a window. Create /etc/systemd/system/reboot-window.service:

INI
[Unit]
Description=Reboot if a kernel update requires it

[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'if [ -f /var/run/reboot-required ]; then /sbin/shutdown -r +5 "Rebooting for kernel update"; fi'

Create /etc/systemd/system/reboot-window.timer:

INI
[Unit]
Description=Weekly reboot window

[Timer]
OnCalendar=Sun 04:00
Persistent=true

[Install]
WantedBy=timers.target

Then enable it:

Bash
sudo systemctl daemon-reload
sudo systemctl enable --now reboot-window.timer

On RHEL-family systems, replace the ExecStart line with a check on dnf needs-restarting -r:

Bash
/usr/bin/bash -c 'if ! /usr/bin/dnf needs-restarting -r >/dev/null 2>&1; then /sbin/shutdown -r +5 "Rebooting for kernel update"; fi'

Tip: Reboots are cheap on a VPS. A five-minute restart in a quiet window is far less painful than running a kernel from six months ago.

Step 5: Hold packages that must not move

Some packages break when they jump a major version, for example a database engine or a panel's PHP stack. Pin them.

On Ubuntu and Debian:

Bash
sudo apt-mark hold mysql-server php8.3-fpm
apt-mark showhold

On AlmaLinux and Rocky Linux:

Bash
sudo dnf install python3-dnf-plugin-versionlock
sudo dnf versionlock add mysql-server
sudo dnf versionlock list

Held packages still receive security patches only if you release the hold and update them manually. Review the list monthly.

Step 6: Confirm logging and alerts

Know where to look when something goes wrong.

  • Ubuntu and Debian: /var/log/unattended-upgrades/unattended-upgrades.log and /var/log/apt/history.log.
  • AlmaLinux and Rocky Linux: journalctl -u dnf-automatic.service and /var/log/dnf.rpm.log.
  • Both: /var/log/secure or /var/log/auth.log for sudo activity around the update window.

If Unattended-Upgrade::Mail is set, make sure local mail is delivered somewhere you read. A cron job that mails you a weekly summary is often simpler than parsing logs by hand — see Automating server maintenance with cron jobs and shell scripting.

Check it works

  1. Force a dry run: sudo unattended-upgrade --dry-run --debug or sudo dnf-automatic --downloadonly.
  2. Confirm the timers are active: systemctl list-timers | grep -E 'apt-daily|unattended|dnf-automatic'.
  3. Wait one full cycle, then compare against your Step 1 baseline. The security list should be empty or much shorter.
  4. Check the reboot flag: cat /var/run/reboot-required or sudo dnf needs-restarting -r.
  5. Reboot once on purpose and confirm the server comes back and your services start. If it does not, you want to learn that now, not during an incident.

Troubleshooting

  • unattended-upgrade reports "No packages found that can be upgraded" but apt list --upgradable shows packages. The pending updates are not from a security origin, or the repo is not in Allowed-Origins. Run the dry run with --debug and read the origin lines.
  • Updates install but the kernel version never changes. Automatic-Reboot is false and no reboot has happened. Check /var/run/reboot-required and your reboot timer.
  • dnf-automatic.timer never fires. The timer is disabled or masked. Run systemctl status dnf-automatic.timer and systemctl enable --now dnf-automatic.timer.
  • A service fails after an automatic update. A package moved to a version your config does not support. Hold that package, roll back from your snapshot, and test the upgrade on a staging VPS before releasing the hold.
  • The disk fills up with old kernels. Enable Remove-Unused-Kernel-Packages and Remove-Unused-Dependencies on Debian-family systems, and check dnf install-only settings on RHEL-family systems.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.