Unpatched software is the number one cause of server compromises. Every week, new vulnerabilities are discovered in the Linux kernel, OpenSSL, SSH, web servers, and system libraries. On a manually maintained server, the window between a vulnerability disclosure and applying the patch is when attackers strike — and they strike fast, often within hours. Ubuntu's unattended-upgrades system closes this window by automatically installing security patches as soon as they're available.

This guide covers the complete automated update pipeline: configuring unattended-upgrades for Ubuntu 24.04, choosing what to auto-update, scheduling automatic reboots when kernel patches require them, setting up email notifications, blacklisting specific packages, monitoring update status, configuring needrestart for automatic service restarts, and combining automated updates with Fail2Ban and firewall rules for a defense-in-depth security strategy.

Prerequisites

Before starting, you need:

Why automatic updates matter: The 2024 XZ Utils backdoor (CVE-2024-3094) was a supply chain attack targeting SSH. The 2021 Log4Shell vulnerability (CVE-2021-44228) was exploited within hours of disclosure. In both cases, servers with automated security updates were protected as soon as patches were released — while manually maintained servers remained vulnerable for days or weeks. Automatic security updates are not optional for production VPS servers.

MassiveGRID Ubuntu VPS Includes

Ubuntu 24.04 LTS pre-installed · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · Independent CPU/RAM/storage scaling · 12 Tbps DDoS protection · 4 global datacenter locations · 100% uptime SLA · 24/7 human support rated 9.5/10

→ Deploy a self-managed VPS — from $1.99/mo
→ Need dedicated resources? — from $19.80/mo
→ Want fully managed hosting? — we handle everything

How Unattended Upgrades Work

Ubuntu's automatic update system has three components:

The flow: apt-daily fetches the latest package lists, then apt-daily-upgrade runs unattended-upgrades, which checks which packages have security updates available and installs them automatically.

Installing and Enabling Unattended Upgrades

Ubuntu 24.04 includes unattended-upgrades by default, but verify it's installed:

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

Enable unattended-upgrades using the interactive configuration tool:

sudo dpkg-reconfigure -plow unattended-upgrades

Select Yes when asked "Automatically download and install stable updates?"

This creates the file /etc/apt/apt.conf.d/20auto-upgrades with:

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

These settings mean:

Verify the timers are active:

sudo systemctl status apt-daily.timer
sudo systemctl status apt-daily-upgrade.timer

Both should show active (waiting).

Configuring What Gets Auto-Updated

The main configuration file is /etc/apt/apt.conf.d/50unattended-upgrades. This controls which package sources are eligible for automatic updates.

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Security Updates Only (Recommended)

The safest configuration only installs security patches. This is the default:

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";
};

On Ubuntu 24.04, this translates to:

Security Updates Plus Bug Fixes

To also include recommended updates (bug fixes, not just security):

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

Adding -updates includes stable release updates (SRU). These are generally safe but occasionally introduce behavioral changes. For most production servers, security-only is the safer choice.

Third-Party Repositories

If you've added third-party repositories (Nginx mainline, MariaDB, Docker, etc.), you can include them in automatic updates. First, find the exact origin and codename:

apt-cache policy | grep -A2 "o="

Or for a specific package:

apt-cache policy nginx | head -10

Then add the appropriate origin to the configuration. For example, to auto-update Docker:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}";
    "${distro_id}:${distro_codename}-security";
    "Docker:${distro_codename}";
};

Automatic Reboot Scheduling

Kernel patches, libc updates, and some security fixes require a reboot to take effect. Without automatic reboots, your server might be running a patched package but still using the vulnerable version in memory.

Enable Automatic Reboots

In /etc/apt/apt.conf.d/50unattended-upgrades:

// Automatically reboot if required after updates
Unattended-Upgrade::Automatic-Reboot "true";

// Reboot at a specific time (avoid peak hours)
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

// Reboot even if users are logged in
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";

With these settings, if an update requires a reboot (kernel patch, glibc update, etc.), the server will automatically reboot at 4:00 AM. This happens only when needed — if no reboot is required, the server keeps running.

Check If a Reboot Is Pending

# Check for the reboot-required flag
cat /var/run/reboot-required 2>/dev/null
# Output: "*** System restart required ***" if pending

# See which packages triggered the reboot requirement
cat /var/run/reboot-required.pkgs 2>/dev/null

Disable Automatic Reboots (High-Availability Servers)

For servers where uptime is critical and you want to schedule reboots manually:

Unattended-Upgrade::Automatic-Reboot "false";

When automatic reboots are disabled, you must monitor for the /var/run/reboot-required flag and schedule reboots during maintenance windows. Our monitoring guide shows how to set up alerts for pending reboots.

For high-availability deployments where even scheduled reboots need to be graceful, MassiveGRID's Managed Dedicated Cloud Servers on a Proxmox HA cluster handle rolling reboots automatically — your workload migrates to another node before the server reboots, maintaining zero downtime.

Email Notifications for Updates

Get notified when packages are automatically installed, when reboots are required, and when errors occur.

Configure Mail Notifications

In /etc/apt/apt.conf.d/50unattended-upgrades:

// Send email notifications
Unattended-Upgrade::Mail "admin@yourdomain.com";

// Send only on error (alternative: "always", "only-on-error", "on-change")
Unattended-Upgrade::MailReport "on-change";

The notification levels:

Install Mail Transport

If you don't have a mail transport agent:

sudo apt install -y mailutils

For reliable email delivery, configure Postfix as a relay through an external SMTP service or use msmtp for a lightweight option:

sudo apt install -y msmtp msmtp-mta
sudo nano /etc/msmtprc
defaults
auth           on
tls            on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile        /var/log/msmtp.log

account        default
host           smtp.mailgun.org
port           587
from           server@yourdomain.com
user           your-smtp-user
password       your-smtp-password
sudo chmod 600 /etc/msmtprc

Test the email configuration:

echo "Test from $(hostname)" | mail -s "Unattended Upgrades Test" admin@yourdomain.com

Blacklisting Specific Packages

Some packages should not be auto-updated because they might break your application or require manual configuration changes after an update.

Package Blacklist

In /etc/apt/apt.conf.d/50unattended-upgrades:

Unattended-Upgrade::Package-Blacklist {
    // Database servers (manual update + test preferred)
    "mariadb-server";
    "mysql-server";
    "postgresql";

    // Web servers (config changes may be needed)
    // "nginx";
    // "apache2";

    // Programming language runtimes
    // "php8.*";
    // "python3";

    // Kernel packages (if you manage reboots manually)
    // "linux-image-*";
    // "linux-headers-*";
};

Uncomment the packages you want to exclude from automatic updates. The reasoning for common blacklist candidates:

Using apt-mark hold

An alternative to the blacklist is holding specific packages at their current version:

# Hold a package (prevents any upgrade, manual or automatic)
sudo apt-mark hold mariadb-server

# Unhold when ready to upgrade
sudo apt-mark unhold mariadb-server

# Show held packages
sudo apt-mark showhold

The difference: the blacklist in 50unattended-upgrades only affects automatic updates — you can still manually upgrade blacklisted packages with apt upgrade. The apt-mark hold prevents the package from being upgraded by any method.

Monitoring Update Status

Unattended Upgrades Log

The primary log file records every automatic update action:

sudo cat /var/log/unattended-upgrades/unattended-upgrades.log

You'll see entries like:

2026-02-27 06:25:10,123 INFO Initial blacklist: mariadb-server
2026-02-27 06:25:10,456 INFO Starting unattended upgrades script
2026-02-27 06:25:15,789 INFO Packages that will be upgraded: libssl3t64 openssl
2026-02-27 06:25:45,012 INFO All upgrades installed

dpkg Log

The dpkg log records every package installation and removal:

sudo grep "upgrade" /var/log/dpkg.log | tail -20

Check Available Security Updates

See what security updates are currently available:

sudo apt update
apt list --upgradable 2>/dev/null | grep -i security

Or use the Ubuntu-specific tool:

sudo apt install -y update-notifier-common
/usr/lib/update-notifier/apt-check --human-readable

Example output:

3 packages can be updated.
2 of these updates are security updates.

Update History Script

Create a script to review recent update activity:

sudo nano /usr/local/bin/update-status.sh
#!/bin/bash
# Ubuntu VPS update status report
echo "=== Update Status Report ==="
echo "Date: $(date)"
echo "Hostname: $(hostname)"
echo ""

echo "-- System Information --"
echo "Ubuntu: $(lsb_release -ds)"
echo "Kernel: $(uname -r)"
echo ""

echo "-- Pending Updates --"
apt list --upgradable 2>/dev/null | grep -v "^Listing"
if [ $? -ne 0 ]; then
    echo "  No pending updates"
fi
echo ""

echo "-- Reboot Required? --"
if [ -f /var/run/reboot-required ]; then
    echo "  YES - reboot required"
    echo "  Triggered by:"
    cat /var/run/reboot-required.pkgs 2>/dev/null | sed 's/^/    /'
else
    echo "  No reboot required"
fi
echo ""

echo "-- Last 10 Automatic Updates --"
grep "Packages that will be upgraded" /var/log/unattended-upgrades/unattended-upgrades.log 2>/dev/null | tail -10
if [ $? -ne 0 ]; then
    echo "  No automatic updates recorded"
fi
echo ""

echo "-- Unattended Upgrades Configuration --"
echo "  Automatic reboot: $(grep 'Automatic-Reboot ' /etc/apt/apt.conf.d/50unattended-upgrades 2>/dev/null | head -1 | tr -d '/' | tr -d ';' | xargs)"
echo "  Reboot time: $(grep 'Automatic-Reboot-Time' /etc/apt/apt.conf.d/50unattended-upgrades 2>/dev/null | head -1 | tr -d '/' | tr -d ';' | xargs)"
echo "  Mail: $(grep 'Unattended-Upgrade::Mail ' /etc/apt/apt.conf.d/50unattended-upgrades 2>/dev/null | head -1 | tr -d '/' | tr -d ';' | xargs)"
echo ""

echo "-- Timer Status --"
systemctl is-active apt-daily.timer > /dev/null 2>&1 && echo "  apt-daily.timer: active" || echo "  apt-daily.timer: INACTIVE"
systemctl is-active apt-daily-upgrade.timer > /dev/null 2>&1 && echo "  apt-daily-upgrade.timer: active" || echo "  apt-daily-upgrade.timer: INACTIVE"

echo ""
echo "-- Held Packages --"
held=$(apt-mark showhold 2>/dev/null)
if [ -n "$held" ]; then
    echo "$held" | sed 's/^/  /'
else
    echo "  None"
fi
sudo chmod +x /usr/local/bin/update-status.sh

Run it anytime:

sudo /usr/local/bin/update-status.sh

For comprehensive monitoring with dashboards and automated alerts, see our Ubuntu VPS monitoring guide.

Needrestart: Automatic Service Restarts

When a shared library (like OpenSSL or glibc) is updated, running services still use the old version loaded in memory. needrestart detects which services need to be restarted and can do so automatically.

Install and Configure needrestart

sudo apt install -y needrestart

Configure automatic restart mode:

sudo nano /etc/needrestart/needrestart.conf

Find the $nrconf{restart} line and set it to automatic:

# Restart mode: 'i' = interactive, 'a' = automatic, 'l' = list only
$nrconf{restart} = 'a';

The modes:

Exclude Services from Automatic Restart

Some services should not be restarted automatically (e.g., database servers, message brokers):

sudo nano /etc/needrestart/needrestart.conf

Find the blacklist section and add services:

# Services to skip for automatic restart
$nrconf{override_rc}{qr(^mariadb)} = 0;
$nrconf{override_rc}{qr(^mysql)} = 0;
$nrconf{override_rc}{qr(^postgresql)} = 0;
$nrconf{override_rc}{qr(^redis)} = 0;

This tells needrestart to never automatically restart MariaDB, MySQL, PostgreSQL, or Redis. You'll restart these manually during maintenance windows.

Check Which Services Need Restart

sudo needrestart -b

The -b flag gives batch-mode output, useful for scripting:

NEEDRESTART-VER: 3.6
NEEDRESTART-KCUR: 6.8.0-45-generic
NEEDRESTART-KEXP: 6.8.0-47-generic
NEEDRESTART-KSTA: 3
NEEDRESTART-SVC: nginx.service
NEEDRESTART-SVC: ssh.service

NEEDRESTART-KSTA: 3 means a kernel update is pending (reboot required). The NEEDRESTART-SVC lines list services needing restart.

Manual Check After Updates

After running manual updates, check what needs attention:

# Check for outdated libraries in running processes
sudo needrestart

# Check only, don't restart anything
sudo needrestart -r l

Combining with Fail2Ban and Firewall for Defense in Depth

Automated updates are one layer of a comprehensive security strategy. Combined with Fail2Ban and firewall rules, they create a defense-in-depth approach that protects against multiple attack vectors.

The Three Layers

Layer Tool Protects Against
Network perimeter UFW firewall Unauthorized access to closed ports
Active defense Fail2Ban Brute-force attacks, scanners, bots
Vulnerability management Unattended upgrades Known software vulnerabilities (CVEs)

Verifying All Three Are Active

Create a security posture check script:

sudo nano /usr/local/bin/security-check.sh
#!/bin/bash
# Security posture check
echo "=== Security Posture Check ==="
echo "Date: $(date)"
echo ""

# 1. Firewall
echo "-- UFW Firewall --"
ufw_status=$(sudo ufw status | head -1)
echo "  Status: $ufw_status"
if echo "$ufw_status" | grep -q "active"; then
    rule_count=$(sudo ufw status numbered | grep -c "^\[")
    echo "  Rules: $rule_count"
else
    echo "  WARNING: Firewall is not active!"
fi
echo ""

# 2. Fail2Ban
echo "-- Fail2Ban --"
if systemctl is-active fail2ban > /dev/null 2>&1; then
    jail_count=$(sudo fail2ban-client status 2>/dev/null | grep "Number of jail" | awk '{print $NF}')
    total_banned=$(sudo fail2ban-client status 2>/dev/null | grep -oP "Currently banned:\s+\K\d+" | awk '{s+=$1} END {print s+0}')
    echo "  Status: active"
    echo "  Active jails: $jail_count"
    echo "  Currently banned IPs: $total_banned"
else
    echo "  WARNING: Fail2Ban is not running!"
fi
echo ""

# 3. Unattended Upgrades
echo "-- Unattended Upgrades --"
if systemctl is-active apt-daily-upgrade.timer > /dev/null 2>&1; then
    echo "  Status: active"
    last_run=$(grep "Starting unattended upgrades" /var/log/unattended-upgrades/unattended-upgrades.log 2>/dev/null | tail -1 | awk '{print $1, $2}')
    echo "  Last run: ${last_run:-unknown}"
else
    echo "  WARNING: Unattended upgrades timer is not active!"
fi
echo ""

# 4. Pending security updates
echo "-- Pending Security Updates --"
sec_updates=$(/usr/lib/update-notifier/apt-check 2>&1 | cut -d';' -f2)
echo "  Security updates available: ${sec_updates:-0}"
echo ""

# 5. Reboot status
echo "-- Reboot Status --"
if [ -f /var/run/reboot-required ]; then
    echo "  REBOOT REQUIRED"
    echo "  Triggered by:"
    cat /var/run/reboot-required.pkgs 2>/dev/null | sed 's/^/    /'
else
    echo "  No reboot required"
fi
echo ""

# 6. SSH configuration
echo "-- SSH Security --"
root_login=$(grep "^PermitRootLogin" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
pass_auth=$(grep "^PasswordAuthentication" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
echo "  Root login: ${root_login:-not set (default: prohibit-password)}"
echo "  Password auth: ${pass_auth:-not set (default: yes)}"
ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
echo "  SSH port: ${ssh_port:-22 (default)}"
sudo chmod +x /usr/local/bin/security-check.sh

Run it regularly to verify your security posture:

sudo /usr/local/bin/security-check.sh

Recommended Security Configuration

For a production Ubuntu VPS, here's the recommended setup combining all three layers:

1. UFW (baseline):

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable

See our network configuration guide for advanced UFW rules.

2. Fail2Ban (active defense):

# Key settings in jail.local
[sshd]
enabled = true
maxretry = 3
bantime = 3600
bantime.increment = true

See our Fail2Ban advanced configuration guide for comprehensive jail setup.

3. Unattended upgrades (vulnerability management):

# Key settings in 50unattended-upgrades
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Mail "admin@yourdomain.com";
Unattended-Upgrade::MailReport "on-change";

Testing Your Update Configuration

Dry Run

Test the unattended-upgrades configuration without actually installing anything:

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

This shows exactly what would happen: which packages would be upgraded, which are blacklisted, and whether any errors occur. Always run this after modifying the configuration.

Manual Trigger

Force an unattended-upgrade run right now:

sudo unattended-upgrade -v

The -v flag enables verbose output so you can see what's happening in real time.

Verify Timer Schedules

# Show when the timers will next run
systemctl list-timers apt-daily apt-daily-upgrade

Example output:

NEXT                         LEFT          LAST                         PASSED       UNIT
Fri 2026-02-27 06:14:00 UTC  8h left       Thu 2026-02-26 06:14:00 UTC  15h ago      apt-daily.timer
Fri 2026-02-27 06:52:00 UTC  8h left       Thu 2026-02-26 06:52:00 UTC  15h ago      apt-daily-upgrade.timer

Customize Timer Schedule

To change when automatic updates run (for example, to a quieter hour):

sudo systemctl edit apt-daily-upgrade.timer
[Timer]
OnCalendar=
OnCalendar=03:00
RandomizedDelaySec=30m

This runs the upgrade at 3:00 AM with up to 30 minutes of random delay (to prevent all servers from hitting repositories simultaneously).

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

Advanced: Periodic Cleanup

Over time, old package versions and cached downloads consume disk space. Configure automatic cleanup:

sudo nano /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";

The additional settings:

You can also set up automatic removal of unused dependencies:

# In 50unattended-upgrades
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";

These options automatically run the equivalent of apt autoremove after updates, keeping your system clean.

Complete Configuration Reference

Here's the recommended /etc/apt/apt.conf.d/50unattended-upgrades for a production VPS:

// Allowed origins for automatic updates
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";
};

// Packages to exclude from auto-update
Unattended-Upgrade::Package-Blacklist {
    "mariadb-server";
    "mysql-server";
    "postgresql";
};

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

// Email notifications
Unattended-Upgrade::Mail "admin@yourdomain.com";
Unattended-Upgrade::MailReport "on-change";

// Cleanup
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";

// Logging
Unattended-Upgrade::SyslogEnable "true";

// Download limit (bytes/sec, 0 = unlimited)
Acquire::http::Dl-Limit "0";

And the recommended /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";

Troubleshooting

Updates Not Running

Check that both timers are active:

systemctl status apt-daily.timer
systemctl status apt-daily-upgrade.timer

If inactive, enable them:

sudo systemctl enable --now apt-daily.timer
sudo systemctl enable --now apt-daily-upgrade.timer

Updates Running But Not Installing Anything

Check the log for clues:

sudo cat /var/log/unattended-upgrades/unattended-upgrades.log

Common causes: all packages are blacklisted, the Allowed-Origins don't match your repositories, or there are no security updates available.

Run a dry run for detailed output:

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

Email Notifications Not Working

Test mail delivery directly:

echo "Test" | mail -s "Test" admin@yourdomain.com

Check the mail log:

sudo tail -20 /var/log/mail.log

Package Conflicts During Auto-Update

If unattended-upgrades encounters a conflict, it skips the problematic package and logs the error. Check:

sudo grep -i "error\|warning\|conflict" /var/log/unattended-upgrades/unattended-upgrades.log

Resolve the conflict manually:

sudo apt update
sudo apt --fix-broken install
sudo apt upgrade

Prefer Managed Security Updates?

If you'd rather not manage update configurations, reboot schedules, package blacklists, and post-update testing yourself, consider MassiveGRID's Managed Dedicated Cloud Servers. The managed service handles all operating system updates, security patching, kernel upgrades, and post-update verification — with zero-downtime rolling reboots on the Proxmox HA cluster. Every managed server includes proactive vulnerability management, 24/7 security monitoring, and incident response from MassiveGRID's operations team.

What's Next