> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Set Up Automatic Security Updates on Ubuntu and Debian
- URL: https://blog.sudowl.com/ubuntu-automatic-updates-unattended-upgrades/
- Published: 2026-03-30T10:00:00.000Z
- Updated: 2026-10-11T08:47:37.000Z
- Description: unattended-upgrades on Ubuntu and Debian: enable it, pick origins, decide on auto-reboot, test with --dry-run, read the logs. Plus the gotchas that bite.
- Author: Lukas
- Tags: Linux, Debian, Ubuntu

Patching by hand works until you have more than three servers. Then it becomes "I'll do it Friday" forever. `unattended-upgrades` is the boring, correct answer: security updates go in daily, without you, and you only get involved when a reboot is needed.

**TL;DR:**

```bash
sudo apt install unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades   # answer Yes
sudo unattended-upgrade --dry-run --debug

```

That's enough to get security updates installed daily on both Ubuntu and Debian. The rest of this post is about the two decisions you still have to make — *what* gets updated and *whether the box reboots itself* — and how to check it's actually running.

## Is it already on?

On Ubuntu Server it usually is. On Debian it depends on the install method — the netinst with "standard system utilities" doesn't always give it to you, many cloud images do. Check:

```bash
apt-config dump APT::Periodic::Update-Package-Lists APT::Periodic::Unattended-Upgrade
systemctl list-timers 'apt-daily*'

```

You want both values at `"1"` and two timers: `apt-daily.timer` (refreshes package lists) and `apt-daily-upgrade.timer` (runs the actual upgrade, early morning with a random delay). If the values are missing or `"0"`, the timers run but do nothing.

## The two files

### /etc/apt/apt.conf.d/20auto-upgrades — the on/off switch

```ini
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";

```

`"1"` means every day. `AutocleanInterval` clears out old downloaded `.deb`s weekly so `/var/cache/apt` doesn't quietly grow. `dpkg-reconfigure` writes the first two lines; I add the third.

### /etc/apt/apt.conf.d/50unattended-upgrades — what and how

This one ships with the package and is heavily commented. Don't rewrite it. Override in your own file so package updates don't fight you over it:

```bash
sudo nano /etc/apt/apt.conf.d/52unattended-upgrades-local

```

Files are read in order and later ones win for scalar values, so `52-` overrides `50-`.

## What gets updated: origins

**Ubuntu** — the default `50unattended-upgrades` already has the right list:

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

```

The plain `${distro_codename}` line is there for packages whose security fix needs a dependency from the release pocket. `-updates` is commented out by default. I leave it that way on servers — security fixes only, bugfix updates I take manually when I'm looking.

**Debian** uses `Origins-Pattern` instead:

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

```

The first line is point-release updates — on Debian stable those are conservative and I keep them on. The `-security` line is the one that matters on Bookworm and newer.

To see which origins your system actually has, look at what apt knows:

```bash
apt-cache policy | grep -E 'o=|origin' | sort -u

```

## Gotcha: third-party repos are not covered

Docker, Temurin, nginx.org, PostgreSQL's PGDG — anything you added yourself is **not** in the allowed list. Those packages never update automatically. That's usually what you want for Docker (an upgrade restarts `dockerd`, which stops your containers unless you've configured `live-restore`), but it means you need a separate habit for them. A monthly `apt list --upgradable` is the minimum.

If you do want one in, find its origin/label with `apt-cache policy` and add it explicitly. Don't blanket-allow everything.

## Blacklisting packages

Some things I never want touched without me watching — usually the database:

```ini
Unattended-Upgrade::Package-Blacklist {
    "mariadb-";
    "postgresql-";
};

```

These are regexes matched against package names, so `"mariadb-"` catches the whole family. Security fixes for those get applied when I do it, with a backup taken first.

## The reboot decision

Library updates (openssl, glibc) need service restarts. Kernel updates need a reboot. unattended-upgrades does neither by default — it installs, then drops `/var/run/reboot-required`.

Two options, both valid:

**Auto-reboot** — for boxes where a two-minute outage at 4am doesn't matter:

```ini
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";

```

`WithUsers "true"` reboots even if someone is logged in. If it's `false` and you leave a tmux session attached, the box never reboots and you won't notice.

**Manual reboot with alerting** — for anything stateful, game servers, anything where players notice. Leave auto-reboot off and check:

```bash
cat /var/run/reboot-required.pkgs 2>/dev/null

```

I'd rather have a check that pings me than a box running a six-week-old kernel because "it'll reboot eventually". Pipe that into whatever alerting you already have — a [systemd service that posts to Discord](/discord-alerts-server-logs-systemd/) works fine for this.

## Gotcha: auto-reboot on a game server

If the box runs a game server under systemd, an automatic reboot is a hard stop for every player online. Most game servers save on SIGTERM, but not all of them save cleanly, and none of them warn players first. For those boxes: auto-reboot off, reboot manually in a maintenance window after an in-game warning.

## Cleanup options

```ini
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Dependencies "false";

```

Old kernels are the classic way `/boot` fills up and the next kernel install fails halfway. Keep `Remove-Unused-Kernel-Packages` on. `Remove-Unused-Dependencies` (the broad one) I leave off — it can remove things you installed as a dependency and later started using directly. If `/boot` or `/` fills anyway: [disk-full forensics](/linux-disk-full-forensics/).

## Mail reports

```ini
Unattended-Upgrade::Mail "root";
Unattended-Upgrade::MailReport "on-change";

```

Only useful if the box can actually send mail. Most of mine can't, so I read the logs instead. `"only-on-error"` is the other sane value.

## Test it — don't wait for tomorrow

```bash
sudo unattended-upgrade --dry-run --debug 2>&1 | less

```

Look for `Allowed origins are:` near the top (your list, after variable expansion) and `Packages that will be upgraded:` near the bottom. If the package list is empty but `apt list --upgradable` shows security updates, your origins are wrong.

To force a real run right now:

```bash
sudo unattended-upgrade -v

```

## Where to look afterwards

```bash
less /var/log/unattended-upgrades/unattended-upgrades.log
less /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
grep -h ' upgrade ' /var/log/dpkg.log | tail -20
journalctl -u apt-daily-upgrade.service --since "3 days ago"

```

The first log tells you what it decided, the second has the dpkg output when something failed. These files are rotated by [logrotate](/logrotate-linux-log-rotation/), so don't expect history from three months back.

## Gotcha: needrestart prompts

On Ubuntu 22.04+ `needrestart` runs after upgrades and checks which services still use old libraries. Interactively it shows a menu; unattended it's supposed to behave, but I've had manual `apt upgrade` sessions sit at that dialog over a flaky SSH connection. To make it just restart services automatically:

```bash
echo "\$nrconf{restart} = 'a';" | sudo tee /etc/needrestart/conf.d/50-autorestart.conf

```

Trade-off: it will restart things like `ssh` and your web server on its own. Usually fine. Not fine for a service that has an expensive restart — those you add to its exclude list.

## Gotcha: the dpkg lock at boot

`apt-daily.timer` fires shortly after boot on a fresh VM. Your provisioning script runs `apt install` at the same moment and dies with `Could not get lock /var/lib/dpkg/lock-frontend`. Either wait for it:

```bash
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do sleep 5; done

```

or add `-o DPkg::Lock::Timeout=300` to your apt calls. Newer apt supports that and it's cleaner than a loop.

## Where this fits

This is step 7 in my [Ubuntu initial setup checklist](/ubuntu-server-initial-setup-checklist/), and it's baked into my [Proxmox cloud-init template](/proxmox-cloud-init-vm-template-ubuntu/) so new VMs start patched. It doesn't replace thinking about security — [SSH hardening](/ssh-hardening-ubuntu-checklist/) and a firewall still matter — but it removes the most common reason servers get owned: a known bug with a fix that was available for months.

---

### Related posts

- [Proxmox + Cloud-Init — Spinning Up an Ubuntu VM in 30 Seconds](https://blog.diengdoh.com/proxmox-cloud-init-vm-template-ubuntu/?ref=blog.sudowl.com)
- [rsync Backups That Actually Work — No Excuses](https://blog.diengdoh.com/rsync-backup-linux/?ref=blog.sudowl.com)
- [Linux Disk Full: Finding What's Eating Your Space](https://blog.diengdoh.com/linux-disk-full-forensics/?ref=blog.sudowl.com)