Automatic security updates on Linux servers
Automatic updates close the gap between “a fix is published” and “the fix is on my server” from days to hours. Every major Linux distribution and FreeBSD ships a tool for it; this guide shows the exact settings for each and how to roll them out without surprise outages.
Should you automate?
For most internet-facing servers, yes, at least for security updates. Attackers often start scanning for a flaw within days of the fix. A server that installs security fixes every night is rarely the one that gets caught.
- Pros: fixes land within about a day; nobody has to remember; small daily changes are easier to debug than a large monthly batch.
- Cons: an update can break an application or change a config default; a library update does not protect you until the programs using it restart; a kernel update needs a reboot; automatic reboots can interrupt work.
A common middle ground: install security updates automatically, keep feature updates manual, and reboot in a planned window. The sections below follow that pattern.
Debian and Ubuntu
The tool is unattended-upgrades. On Ubuntu it is normally installed already. On Debian, install and enable it:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Answer “Yes”. This writes /etc/apt/apt.conf.d/20auto-upgrades with these two lines (the number is “every N days”; 0 turns it off):
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
What gets installed is set in /etc/apt/apt.conf.d/50unattended-upgrades. Debian’s default pattern covers the security archive and stable point-release updates. Ubuntu’s default covers the release, its -security pocket and, if Ubuntu Pro is attached, the ESM security pockets; general -updates stay commented out. Packages from third-party repositories are not upgraded unless you add their origin.
Do not edit the shipped file. The upstream documentation recommends a local file that sorts later, so package updates do not conflict with your changes:
sudo nano /etc/apt/apt.conf.d/52unattended-upgrades-local
// Reboot by itself if /var/run/reboot-required exists after an upgrade
Unattended-Upgrade::Automatic-Reboot "true";
// Do not reboot while someone is logged in
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
// Reboot at 03:30 local time instead of "now" (the default)
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
// Report by mail (needs a working mail setup, e.g. mailx + an MTA)
Unattended-Upgrade::Mail "ops@example.com";
Unattended-Upgrade::MailReport "only-on-error";
// Clean up old kernels so /boot does not fill up
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Automatic-Reboot is off by default. Turn it on only if an unplanned reboot at 03:30 is acceptable for that server. MailReport accepts always, only-on-error or on-change.
Test without changing anything, then read the log:
sudo unattended-upgrade --dry-run --debug
less /var/log/unattended-upgrades/unattended-upgrades.log
Service restarts on Ubuntu. Since Ubuntu 24.04 LTS, needrestart restarts affected services automatically after updates by default. Services it must never touch go in a file under /etc/needrestart/conf.d/, not in the main config.
RHEL, AlmaLinux, Rocky and Oracle Linux
RHEL 8, 9 and 10 and their rebuilds use dnf-automatic:
sudo dnf install dnf-automatic
sudo nano /etc/dnf/automatic.conf
Set the [commands] section for security-only installs:
[commands]
upgrade_type = security
download_updates = yes
apply_updates = yes
Then enable the timer that follows the config file:
sudo systemctl enable --now dnf-automatic.timer
systemctl list-timers 'dnf-automatic*'
Red Hat also ships timers with a fixed behavior that override apply_updates and download_updates: dnf-automatic-notifyonly.timer (report only), dnf-automatic-download.timer (download, do not install) and dnf-automatic-install.timer (download and install). Enable only one of them.
upgrade_type = security only works when the repository publishes advisory metadata. Check what it would pick up with sudo dnf updateinfo list --security. If that list is empty while updates exist, security filtering will install nothing.
Newer dnf-automatic versions also accept reboot = never | when-changed | when-needed and a reboot_command (default: shutdown -r +5 with a message). Check man dnf-automatic on your release before relying on it.
Fedora and DNF5
Fedora 41 and later use DNF5, and the automatic tool changed. The package is dnf5-plugin-automatic (it also provides the name dnf-automatic):
sudo dnf install dnf5-plugin-automatic
sudo cp /usr/share/dnf5/dnf5-plugins/automatic.conf /etc/dnf/automatic.conf
sudo nano /etc/dnf/automatic.conf
sudo systemctl enable --now dnf5-automatic.timer
The differences that catch people out:
- Config path: defaults live in
/usr/share/dnf5/dnf5-plugins/automatic.conf(do not edit). Your changes go in/etc/dnf/automatic.conf, which is read after the defaults, so it may contain only the lines you change. - One timer: use
dnf5-automatic.timer. The DNF4 variants-install,-downloadand-notifyonlyare not shipped; behavior comes from the config file. - Defaults:
download_updatesis on,apply_updatesis off, andemit_viais onlystdio(DNF4 also used email and motd). - Options:
upgrade_typeacceptsdefault,securityordistro-sync;rebootacceptsnever,when-changedorwhen-needed(for example after a kernel or systemd update).
[commands]
apply_updates = yes
upgrade_type = security
reboot = when-needed
reboot_command = "shutdown -r +5 'Rebooting after applying package updates'"
SUSE and openSUSE
SUSE delivers fixes as patches with a category (security, recommended, optional) and a severity. To install only security patches by hand:
sudo zypper list-patches --category security
sudo zypper --non-interactive patch --category security --with-interactive
If zypper exits with code 103, it updated itself first; run the same command again. sudo zypper needs-rebooting exits with 102 when a reboot is suggested.
SLES 15 and Leap 15 with YaST: run sudo yast2 online_update_configuration (package yast2-online-update-configuration), pick Daily, and under “Filter by Category” select Security. SUSE’s guide notes that these automatic updates never reboot the system.
openSUSE Leap 16 and Tumbleweed: the os-update package runs daily from os-update.timer. Put only the values you change in /etc/os-update.conf:
sudo zypper install os-update
echo 'UPDATE_CMD="security"' | sudo tee /etc/os-update.conf
echo 'REBOOT_CMD="none"' | sudo tee -a /etc/os-update.conf
sudo systemctl enable --now os-update.timer
With REBOOT_CMD="none" it only prints that a reboot is needed; the default auto reboots through rebootmgr or systemctl reboot.
Any SUSE release, including SLES 16: a small systemd timer of your own works everywhere. Create /etc/systemd/system/zypper-security.service:
[Unit]
Description=Install SUSE security patches
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
SuccessExitStatus=102 103
ExecStart=/usr/bin/zypper --non-interactive patch --category security --with-interactive --auto-agree-with-licenses
ExecStart=/usr/bin/zypper --non-interactive patch --category security --with-interactive --auto-agree-with-licenses
--with-interactive stops zypper from skipping patches that need a reboot, such as kernel fixes; without it, non-interactive mode skips them. The second run installs anything left after a zypper self-update. Then /etc/systemd/system/zypper-security.timer:
[Unit]
Description=Daily SUSE security patches
[Timer]
OnCalendar=*-*-* 03:30
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now zypper-security.timer
Alpine Linux
Alpine has no security-only filter, but stable branches only get security patches for the versions they ship, so upgrading within a branch is low-risk. Use the cron daemon’s daily directory. The default root crontab runs /etc/periodic/daily at 02:00. As root:
cat > /etc/periodic/daily/apk-upgrade <<'EOF'
#!/bin/sh
apk update -q && apk upgrade -q
EOF
chmod +x /etc/periodic/daily/apk-upgrade
rc-update add crond default
rc-service crond start
A new linux-lts or linux-virt kernel still needs a reboot, which this script does not do.
FreeBSD
The base system and packages update separately. For the base system, add this line to /etc/crontab:
@daily root freebsd-update cron
freebsd-update cron waits a random 1 to 3,600 seconds, downloads any patches and mails root. It does not install them. Apply them as root and, if a kernel patch was included, reboot (the last command reboots the server):
freebsd-update install
freebsd-version -k; uname -r # different? reboot
shutdown -r now
freebsd-update rollback undoes the last install. On FreeBSD 15 systems installed with base packages (pkgbase, a tech preview), update the base with pkg upgrade -r FreeBSD-base instead.
For third-party packages, a daily vulnerability check is a safe first step; schedule the upgrade itself in your maintenance window:
0 5 * * * root pkg audit -F
30 3 * * 2 root pkg upgrade -y
pkg does not restart services; restart them afterwards (see Restarts and reboots).
Roll out safely
- Staging first. Point one or two test servers, which run the same packages as production, at the same repositories and let them update a day earlier. Most breakage shows up there.
- Stagger production. Split servers into groups with different timer times, so one bad update never hits every node of a cluster at once.
- Set a maintenance window. Use timer times (03:30,
Automatic-Reboot-Time,OnCalendar) that match the window your users expect, and never reboot all members of a cluster together. - Monitor results. Send reports somewhere a person reads them, alert on failed timer runs (
systemctl list-timers,systemctl status) and on servers that need a reboot for more than a few days. - Know how to go back. Take a snapshot before risky windows; know
dnf history undo,freebsd-update rollbackor your snapshot tool before you need them. - Do not stop at automation. A critical, actively exploited flaw should be patched by hand the same day; see Emergency patching.
Automation does not fix an unsupported release. Once your release is end-of-life, the timers keep running but no new fixes arrive. Check your release on the Linux and Unix pages.
Sources
- Automatic updates, Ubuntu Server documentation, Canonical
- UnattendedUpgrades, Debian Wiki
- unattended-upgrades 2.12 README (Debian 13), Debian Sources
- 50unattended-upgrades default configuration (Debian 13), Debian Sources
- Automating software updates in RHEL 10, Red Hat
- DNF Automatic, DNF documentation
- Automatic command (dnf5-automatic), DNF5 documentation
- dnf5.spec (package and file layout), rpm-software-management on GitHub
- zypper(8) manual page, openSUSE
- YaST Online Update, SLES 15 SP7 documentation, SUSE
- os-update(8), openSUSE Leap 16.0 manual pages
- Working with the Alpine Package Keeper, Alpine Linux User Handbook
- apk-upgrade(8), apk-tools
- Default root crontab (alpine-baselayout), Alpine aports
- Updating and Upgrading FreeBSD, FreeBSD Handbook
- freebsd-update(8), FreeBSD manual pages
- pkg-audit(8), FreeBSD manual pages