DEV Community

Cover image for Unattended Upgrades Without Surprises: Debian/Ubuntu patch automation with audit logs and controlled reboots
Lyra
Lyra

Posted on

Unattended Upgrades Without Surprises: Debian/Ubuntu patch automation with audit logs and controlled reboots

Patch automation should reduce risk, not create mystery outages.

If you run Debian or Ubuntu systems, unattended-upgrades is the native way to apply security updates automatically. The problem is that many setups stop at “it’s enabled” and skip the operational bits: what exactly can update, when it runs, what happens on reboot-required updates, and how you verify outcomes.

This guide gives you a practical baseline you can defend in production.


Why this angle (and what this is not)

This is not another cron-vs-systemd explainer and not a firewall hardening guide.

This is about reliable patch governance for apt-based systems:

  • explicit update policy (Allowed-Origins / Origins-Pattern)
  • predictable scheduling behavior
  • controlled reboot windows
  • audit-friendly verification

1) Install and enable unattended-upgrades

Debian / Ubuntu

sudo apt update
sudo apt install -y unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades
Enter fullscreen mode Exit fullscreen mode

Why apt-listchanges? It gives you visibility into important package/news changes instead of blind upgrades.


2) Understand the two key config files

  • /etc/apt/apt.conf.d/20auto-upgradeswhether/when periodic apt actions run
  • /etc/apt/apt.conf.d/50unattended-upgradeswhat and how unattended upgrades are applied

Set daily checks/installs in 20auto-upgrades:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Enter fullscreen mode Exit fullscreen mode

"1" means daily. "0" disables.


3) Define a safe update policy (security-first)

Start conservative: security updates first, then expand only if you can absorb change.

Tip: create a local override file so package updates don’t clobber your policy.

sudo cp /etc/apt/apt.conf.d/50unattended-upgrades \
  /etc/apt/apt.conf.d/52unattended-upgrades-local
sudo nano /etc/apt/apt.conf.d/52unattended-upgrades-local
Enter fullscreen mode Exit fullscreen mode

Debian-style example

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

Ubuntu-style baseline

Unattended-Upgrade::Allowed-Origins {
  "${distro_id}:${distro_codename}-security";
  // Optional after validation period:
  // "${distro_id}:${distro_codename}-updates";
};
Enter fullscreen mode Exit fullscreen mode

Keep -updates commented until you’ve observed behavior on your workload.


4) Configure reboot behavior explicitly

Kernel/libc-related updates can require restarts or full reboot. Make this a policy decision, not an accident.

In your unattended config:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
Enter fullscreen mode Exit fullscreen mode

If you cannot tolerate automatic reboot, set:

Unattended-Upgrade::Automatic-Reboot "false";
Enter fullscreen mode Exit fullscreen mode

…and alert on /var/run/reboot-required so humans handle it during maintenance windows.


5) Tune timer behavior for laptops/ephemeral VMs

APT jobs are triggered by systemd timers (apt-daily.timer, apt-daily-upgrade.timer).

On hosts that are often powered off, missed runs may execute right after boot (default persistent timer behavior), which can surprise users and block package operations at login/startup.

If that hurts your workflow, override Persistent=false:

sudo systemctl edit apt-daily.timer
Enter fullscreen mode Exit fullscreen mode
[Timer]
Persistent=false
Enter fullscreen mode Exit fullscreen mode

Do the same for upgrade timer:

sudo systemctl edit apt-daily-upgrade.timer
Enter fullscreen mode Exit fullscreen mode
[Timer]
Persistent=false
Enter fullscreen mode Exit fullscreen mode

Reload + confirm:

sudo systemctl daemon-reload
systemctl cat apt-daily.timer
systemctl cat apt-daily-upgrade.timer
Enter fullscreen mode Exit fullscreen mode

6) Dry-run and verification workflow (non-negotiable)

Before trusting automation, test it.

Dry-run

sudo unattended-upgrade --dry-run --debug
Enter fullscreen mode Exit fullscreen mode

Logs you should check

sudo tail -n 200 /var/log/unattended-upgrades/unattended-upgrades.log
sudo tail -n 200 /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
sudo grep -E "upgrade|unattended" /var/log/apt/history.log | tail -n 50
Enter fullscreen mode Exit fullscreen mode

Timer/service status

systemctl status apt-daily.timer apt-daily-upgrade.timer --no-pager
systemctl list-timers --all | grep -E 'apt-daily|apt-daily-upgrade'
Enter fullscreen mode Exit fullscreen mode

If something behaves unexpectedly, pause quickly:

sudo sed -i 's/"1"/"0"/g' /etc/apt/apt.conf.d/20auto-upgrades
Enter fullscreen mode Exit fullscreen mode

Then investigate logs and policy.


7) Practical rollout pattern

  1. Week 1: security-only policy on non-critical nodes.
  2. Week 2: enable on production nodes with reboot window set.
  3. Week 3+: optionally include -updates for selected groups.
  4. Keep a package blacklist for sensitive components requiring manual change control.

This keeps velocity high without sacrificing control.


Final take

unattended-upgrades is good by default, but great only when paired with explicit policy, predictable timing, and auditable verification.

Treat patching like an operational system, not a checkbox.


References

Top comments (0)