Linux & Systems

Unattended Upgrades You Can Actually Check

Enable automatic security updates with unattended-upgrades on Debian and Ubuntu, see exactly what it will install, pick a reboot policy, and confirm it really ran.

6 min read
debianubuntusecurityautomationsystemd

Low morning sun casting beams through fog drifting over pine trees and rooftops, seen from above

Small servers rarely fall behind on patches because someone decided against patching. They fall behind because patching is a chore nobody put on a calendar. unattended-upgrades does the chore: once a day it installs updates from the repositories you allow and writes down what it did.

The aim here is a setup you can check instead of trust. Confirm it’s on, see which repositories it pulls from, preview a run, choose how reboots happen, and know where to look the next morning. Commands match Debian 13 (trixie) and Ubuntu 24.04 and 26.04 LTS.

See what’s already there

Ubuntu installs and enables it by default. On Debian it depends on how the image was built, so look before you install anything:

dpkg -s unattended-upgrades | grep -E '^(Status|Version)'
apt-config dump APT::Periodic

On a Debian 13 machine where it’s working:

Status: install ok installed
Version: 2.12
APT::Periodic "";
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

apt-config dump prints the merged result of everything in /etc/apt/apt.conf.d/, so you don’t have to work out which file wins. If dpkg -s reports is not installed, install it. If it’s installed but those two values are "0" or missing, it’s present and switched off.

Install and enable

sudo apt update
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

The package asks “Automatically download and install stable updates?” during installation, but it’s a low-priority question, so it’s normally answered silently with the default. On Debian 13 and Ubuntu 26.04 that default is yes. -plow makes the question visible so you answer it on purpose. Saying yes writes /etc/apt/apt.conf.d/20auto-upgrades:

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

The numbers are intervals in days. Saying no rewrites them as "0", which is also the off switch later.

What it will install

The policy lives in /etc/apt/apt.conf.d/50unattended-upgrades. On Debian, the active part is:

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

Each pattern is matched against the metadata of your configured repositories. You can print that metadata and compare:

apt-cache policy | grep -E '^ +release'
     release a=now
     release v=13,o=Debian,a=stable-security,n=trixie-security,l=Debian-Security,c=main,b=amd64
     release v=13-updates,o=Debian,a=stable-updates,n=trixie-updates,l=Debian,c=main,b=amd64
     release v=13.7,o=Debian,a=stable,n=trixie,l=Debian,c=main,b=amd64

Line them up and trixie-updates matches nothing, so it’s skipped. The security archive matches, and so does plain trixie. That main archive only changes at Debian point releases, so the Debian default means “security fixes plus point-release updates,” not strictly security-only.

Ubuntu uses the older Allowed-Origins syntax for the same idea:

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

-updates is commented out further down the list. The release pocket is included so that a security fix can pull in a new dependency, and the ESM lines only matter on machines attached to Ubuntu Pro. Third-party repositories, such as Docker’s or PostgreSQL’s, aren’t covered on either distribution unless you add a pattern for them.

To change anything, leave 50unattended-upgrades alone and put overrides in a small file that sorts after it, such as /etc/apt/apt.conf.d/52unattended-upgrades-local. APT reads that directory in alphanumeric order, so a single value set later wins. Lists behave differently: a pattern you add gets appended to the default list rather than replacing it. apt-config dump Unattended-Upgrade shows the merged result.

Preview a run

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

--dry-run downloads packages but doesn’t install them. The output is chatty, and two lines matter:

Allowed origins are: origin=Debian,codename=trixie,label=Debian, origin=Debian,codename=trixie,label=Debian-Security, origin=Debian,codename=trixie-security,label=Debian-Security
Packages that will be upgraded: libssl3t64 openssl

On a fully patched machine the second line becomes No packages found that can be upgraded unattended and no pending auto-removals. That’s the boring answer you’re hoping for. The Marking not allowed lines that mention trixie-updates are the tool showing its work. If a package you expected doesn’t appear, compare its repository’s release line with the patterns.

Where it writes things down

  • /var/log/unattended-upgrades/unattended-upgrades.log: what it decided and why.
  • /var/log/unattended-upgrades/unattended-upgrades-dpkg.log: dpkg output for those installs.
  • /var/log/dpkg.log: every package change on the system, whoever made it.

The first directory is readable only by root and the adm group:

sudo tail -n 20 /var/log/unattended-upgrades/unattended-upgrades.log
2026-10-12 06:41:07,118 INFO Starting unattended upgrades script
2026-10-12 06:41:07,119 INFO Allowed origins are: origin=Debian,codename=trixie,label=Debian, ...
2026-10-12 06:41:12,804 INFO Packages that will be upgraded: libssl3t64 openssl
2026-10-12 06:41:12,805 INFO Writing dpkg log to /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
2026-10-12 06:41:31,377 INFO All upgrades installed

If you see Installing the upgrades failed!, the dpkg log is the next file to read.

Check that it really ran

There’s no cron job involved. Two systemd timers drive everything. apt-daily.timer refreshes package lists around 06:00 and 18:00 with up to 12 hours of random delay. apt-daily-upgrade.timer runs the upgrade around 06:00 with up to an hour of delay. Both are Persistent=true, so a run missed while the machine was off happens after it boots.

systemctl list-timers 'apt-daily*'
journalctl -u apt-daily-upgrade.service --since "3 days ago"

In list-timers, the LAST column should be within the past day or so. In the journal you want a start and a Finished apt-daily-upgrade.service line for each day. If the timer fires on schedule but the log above never changes, check 20auto-upgrades again: with "0" values the timer runs, finds nothing to do, and finishes successfully.

To move the schedule, add a drop-in for the timer as described in systemctl cat and drop-in overrides. OnCalendar= is a list, so clear it with an empty OnCalendar= line before adding your own. systemd timers without the guesswork covers the calendar syntax.

Reboots

New files land on disk straight away, but a running kernel stays the one you booted. When an update needs a reboot, a flag file appears, along with a list of the packages that asked for it:

cat /var/run/reboot-required.pkgs 2>/dev/null || echo "no reboot pending"

On Debian, unattended-upgrades creates this flag through its own kernel hook whenever a new kernel is installed. What happens next depends on three settings, which belong in your 52unattended-upgrades-local:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
  • Automatic-Reboot defaults to false, so the machine never reboots on its own. That’s safe in the same way that never opening your mail is safe. If you leave it off, put reboots on your own calendar.
  • Automatic-Reboot-Time defaults to now, which means right after the morning upgrade. The value is handed to shutdown -r, and a time that has already passed today rolls over to tomorrow. Because the upgrade runs after 06:00, 03:30 means the following night. sudo shutdown -c cancels a pending reboot.
  • Automatic-Reboot-WithUsers defaults to true. Set it to false and the reboot is skipped whenever the users command prints anyone. Run users now to see what it sees. An SSH session you forgot in another tab counts, and it can postpone reboots indefinitely.

Before you enable automatic reboots, make sure everything comes back without help: services enabled at boot, and containers with a restart policy that survives a daemon restart (see Docker restart policies without the magic). Library updates are a separate case. A service keeps using the old OpenSSL until that service restarts, and Debian’s needrestart package can list which ones are affected.

Turning it back off

  • Run sudo dpkg-reconfigure -plow unattended-upgrades and answer no. 20auto-upgrades switches to "0".
  • Delete 52unattended-upgrades-local to go back to the packaged defaults.
  • Run sudo shutdown -c if a reboot is already scheduled.

References

Cover photo: Unsplash.