
Unattended Upgrades on Ubuntu and Debian: Auto Security Patches
Most servers that get broken into are not hacked by a genius. They are found by a bot that scans the whole internet for a vulnerability that was fixed weeks ago, on machines where nobody ran apt upgrade. The fix existed, it was free, and it was one command away. The server simply never got it.
The good news is that Debian and Ubuntu ship a tool that closes this gap on its own: unattended-upgrades. In this guide we set it up on a fresh VPS so that it installs security patches every day, tells you what it did (in a log file and, if you want, by email), and reboots the server at a quiet hour when a kernel update needs it. Everything here was written for Debian 13 (trixie) and Ubuntu 24.04 LTS (noble), and we point out every place where the two behave differently.
The tool itself is developed in the open at github.com/mvo5/unattended-upgrades and is packaged by both distributions, so there is nothing to download from third parties: it all comes from the official repositories you already use.
What unattended-upgrades actually does
A quick vocabulary check first. Software on Debian and Ubuntu comes as packages: archives that contain a program plus instructions on how to install it. Packages are published in repositories, servers (mirrors) that hold the packages and an index of which versions exist. The tool that talks to repositories is apt.
When maintainers fix a security hole in, say, OpenSSL or OpenSSH, they publish a new version of that package to a dedicated security repository. On Debian 13 it is called trixie-security, on Ubuntu 24.04 it is the noble-security pocket. Your server only gets the fix when apt downloads and installs it. Until then, the hole is open, even though the patch is sitting on a mirror a few milliseconds away.
unattended-upgrades is a small Python program that does that installation for you, but only from the repositories you allow, and only for packages that do not need a human to answer a question (for example “keep your modified config file or take the new one?”). It is not a daemon that runs all the time. It is started by two systemd timers. A timer is systemd’s built-in equivalent of a cron job: a rule that says “start this service at this time”.
apt-daily.timerruns twice a day, refreshes the package lists and downloads what is available.apt-daily-upgrade.timerruns once a day and startsunattended-upgrade, which installs the allowed updates.
What it does not do
It is worth being clear about the limits, so nothing surprises you later:
- It never upgrades your release. Debian 13 stays Debian 13, Ubuntu 24.04 stays 24.04. Moving to the next major version is always a manual, planned job.
- It ignores repositories you did not allow. Packages from third-party repositories (Docker, a PHP repository, a database vendor) are not touched unless you add them explicitly.
- It does not swap the running kernel. The kernel is the core of the operating system, the part that is loaded at boot and talks to the hardware. A new kernel package is installed on disk, but the old one keeps running until the next reboot. That is why we configure automatic reboots in step 6.
- It is not a security strategy on its own. A firewall, SSH keys instead of passwords, and backups still matter. unattended-upgrades closes the “known and already fixed” category of holes, which happens to be the most exploited one.
Why automatic security updates are worth it
The math is simple. If you patch by hand once a month, a fix published at a random moment waits on average about 15 days before it reaches your server. Patching weekly brings that to about 3.5 days. With unattended-upgrades running daily, the wait drops to well under a day, because the only delay left is the time until the next timer run.
Meanwhile, automated scanners usually start probing for a newly published vulnerability within days, sometimes hours. The longer your window, the more likely it is that a bot gets there first.
“But what if an update breaks something?” is the usual objection, and it is a fair question. Stable distributions handle security differently from rolling ones: the Debian and Ubuntu security teams backport the fix into the same version you already run, instead of shipping a new major version with new behavior. On the servers we manage, breakage caused by a security update is rare, much rarer than incidents caused by a missing one. For the few packages where you want full control, you can hold them back, and we show how.
Debian 13 vs Ubuntu 24.04: the differences that matter
The tool is the same on both, but the defaults around it are not. Keep this table in mind while following the steps:
| Topic | Debian 13 (trixie) | Ubuntu 24.04 (noble) |
|---|---|---|
| Installed by default | Depends on the image, often missing on minimal installs | Yes, and already enabled |
| Origins setting in 50unattended-upgrades | Origins-Pattern | Allowed-Origins |
| What is allowed by default | Security updates plus stable point releases | Security updates (plus ESM if you use Ubuntu Pro) |
Who creates /run/reboot-required | A kernel hook shipped by unattended-upgrades, mainly after kernel updates | update-notifier-common, after kernel and some core library updates |
/run/reboot-required.pkgs | May be missing | Usually present, lists the packages |
| needrestart | Not installed by default | Installed by default |
/var/log/syslog | Not present by default, use journalctl | Present (rsyslog installed) |
Note that /var/run is a symbolic link to /run on both systems, so /var/run/reboot-required and /run/reboot-required are the same file.
What you need
- A VPS running Debian 13 or Ubuntu 24.04, either KVM or LXC (we cover the LXC differences below).
- SSH access as a user with
sudorights. - About 15 minutes.
- Optional, for email reports: an SMTP account (your email provider, or a transactional email service) with a username and password.
- Recommended: a recent backup or snapshot of the server, as you would before any configuration change.
Here is the plan. Each numbered step below matches one heading in the rest of the guide.
Step 1: Check what is already there
Before changing anything, let’s see which system we are on and whether unattended-upgrades is already installed:
grep PRETTY_NAME /etc/os-release
dpkg -l unattended-upgrades 2>/dev/null | grep '^ii' || echo "unattended-upgrades is not installed"
ls /etc/apt/apt.conf.d/ | grep -E 'auto-upgrades|unattended' || echo "no unattended-upgrades config yet"
systemd-detect-virt
What each line tells you:
- The first line prints something like
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"orPRETTY_NAME="Ubuntu 24.04.3 LTS". - The second prints a line starting with
ii unattended-upgradesif the package is installed, otherwise our “not installed” message. - The third lists
20auto-upgradesand50unattended-upgradesif they exist. On Ubuntu you will usually see both already. - The last prints the virtualization type:
kvmon a KVM VPS,lxcinside an LXC container.
If you are on Ubuntu and everything is already there, do not skip the rest: the default setup installs updates but sends no reports and never reboots, so a kernel fix can sit on disk for months without being active.
Step 2: Install unattended-upgrades
Refresh the package lists and install the package (on Ubuntu this simply confirms it is installed):
sudo apt update
sudo apt install -y unattended-upgrades
apt update downloads the latest package indexes from the repositories; apt install installs the tool and its dependencies. The output should end without errors. On Debian, the package also drops a small hook into /etc/kernel/postinst.d/ that creates the reboot flag after a kernel is installed, which we rely on in step 6.
Step 3: Turn on the daily schedule
Installing the package is not enough: apt has to be told to run it periodically. The friendliest way is the built-in configuration dialog:
sudo dpkg-reconfigure -plow unattended-upgrades
When asked “Automatically download and install stable updates?”, choose Yes. This writes the file /etc/apt/apt.conf.d/20auto-upgrades. If you prefer to write it by hand (useful in scripts), this does the same thing and also cleans the downloaded package cache once a week:
sudo tee /etc/apt/apt.conf.d/20auto-upgrades > /dev/null <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
The "1" values mean “every 1 day”. Now confirm that apt sees these settings and that both timers are active:
apt-config dump APT::Periodic
systemctl list-timers 'apt-daily*'
The first command should include APT::Periodic::Update-Package-Lists "1"; and APT::Periodic::Unattended-Upgrade "1";. The second shows two rows, apt-daily.timer and apt-daily-upgrade.timer, each with a NEXT time in the future. If a timer is missing, enable it with sudo systemctl enable --now apt-daily-upgrade.timer (or apt-daily.timer).
Step 4: Choose which updates get installed
The main configuration lives in /etc/apt/apt.conf.d/50unattended-upgrades. Open it to read it, but we will not edit it: when the package gets a new version of this file, your changes would trigger a “which version do you want to keep?” question, and that question blocks automatic updates of the tool itself. Instead, we put our own settings in a separate file that is read afterwards. apt reads apt.conf.d files in alphabetical order, so a file named 52... overrides values from 50....
First, look at which origins (repositories) are allowed. On Debian 13 you will find:
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";
};
On Ubuntu 24.04 the same idea uses a different key:
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";
};
${distro_codename} is replaced automatically with trixie or noble. On Debian, the first line also brings in stable point releases, the conservative bug fix bundles Debian publishes every couple of months. We recommend leaving it on. On Ubuntu, the ESM lines only matter if the machine is attached to Ubuntu Pro; otherwise they match nothing and are harmless. The -updates lines are commented out on both, which is a sensible default for servers: security only, nothing experimental. Never enable -proposed or -backports here.
To see the exact origin names your server knows about, run:
apt-cache policy | grep -E 'release' | sort -u
You will see lines such as release v=13,o=Debian,a=stable-security,n=trixie-security,l=Debian-Security,c=main. Those o= (origin), n= (codename) and l= (label) values are what the patterns match.
Now create our local override file with some housekeeping options:
sudo tee /etc/apt/apt.conf.d/52unattended-upgrades-local > /dev/null <<'EOF'
// Local overrides for unattended-upgrades
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::SyslogEnable "true";
EOF
The first option removes old kernels after a new one is installed, so /boot and the disk do not fill up over time. The second removes dependencies that became unused because of an automatic upgrade. The third also sends the tool’s messages to the system journal.
If there is a package you never want updated automatically (a database you upgrade only during maintenance windows, for example), the simplest method is an apt hold, which unattended-upgrades respects:
sudo apt-mark hold mariadb-server
apt-mark showhold
Undo it later with sudo apt-mark unhold mariadb-server. Use holds sparingly: a held package also stops receiving security fixes.
Step 5: Get email reports
Logs are good, but an email after each change is better: you see what was patched without logging in. unattended-upgrades sends mail through the standard sendmail command, so we need a small program that provides it. We use msmtp, a lightweight client that forwards mail to a real SMTP server instead of running a full mail server on your VPS:
sudo apt install -y msmtp-mta bsd-mailx ca-certificates
msmtp-mta provides /usr/sbin/sendmail, bsd-mailx provides the mail command for testing. If the installer asks whether to enable AppArmor support for msmtp, the default answer is fine. Now write the configuration, replacing the host, addresses and password with the details of your SMTP account:
sudo tee /etc/msmtprc > /dev/null <<'EOF'
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
syslog LOG_MAIL
account default
host smtp.example.com
port 587
from server1@example.com
set_from_header on
user server1@example.com
password CHANGE_ME
EOF
sudo chown root:root /etc/msmtprc
sudo chmod 600 /etc/msmtprc
Port 587 is the standard “submission” port for authenticated sending, which works far better than trying to deliver directly on port 25. set_from_header on makes every message use your real sender address, which most SMTP providers require. The file contains a password, so only root may read it. Send a test:
echo "Test from $(hostname)" | sudo mail -s "msmtp test" you@example.com
If it arrives, tell unattended-upgrades where to send reports:
sudo tee -a /etc/apt/apt.conf.d/52unattended-upgrades-local > /dev/null <<'EOF'
Unattended-Upgrade::Mail "you@example.com";
Unattended-Upgrade::Sender "server1@example.com";
Unattended-Upgrade::MailReport "on-change";
EOF
MailReport accepts always, only-on-error or on-change. We like on-change: you get an email only when something was installed or something failed, so the messages stay meaningful instead of becoming daily noise.
Step 6: Reboot automatically at a safe hour
Most updates take effect immediately: when a library like OpenSSL is upgraded, the affected services are restarted and pick up the new code. The kernel is different, it only changes at boot. So we let unattended-upgrades reboot the server, but only when needed, and only at an hour when nobody is using it.
First, make sure the server’s clock uses your local time zone, otherwise “04:00” means 04:00 UTC:
timedatectl
sudo timedatectl set-timezone Europe/Bucharest
Then add the reboot settings:
sudo tee -a /etc/apt/apt.conf.d/52unattended-upgrades-local > /dev/null <<'EOF'
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
EOF
apt-config dump Unattended-Upgrade | grep -E 'Mail|Reboot|Remove'
The last command prints the effective values, so you can confirm the overrides were picked up. After each run, unattended-upgrades checks for /run/reboot-required. If the file exists, it schedules a reboot at 04:00; if not, nothing happens. Automatic-Reboot-WithUsers "true" means it reboots even if someone forgot an SSH session open, which is what you want on a server.
By default, apt-daily-upgrade.timer runs around 06:00, which means a kernel installed in the morning would wait almost a full day for the 04:00 reboot. Let’s move the upgrade to 03:00, and the list refresh a bit earlier, so the whole cycle happens in one quiet night:
sudo mkdir -p /etc/systemd/system/apt-daily-upgrade.timer.d /etc/systemd/system/apt-daily.timer.d
sudo tee /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf > /dev/null <<'EOF'
[Timer]
OnCalendar=
OnCalendar=*-*-* 03:00
RandomizedDelaySec=20m
EOF
sudo tee /etc/systemd/system/apt-daily.timer.d/override.conf > /dev/null <<'EOF'
[Timer]
OnCalendar=
OnCalendar=*-*-* 01:00
OnCalendar=*-*-* 13:00
RandomizedDelaySec=30m
EOF
sudo systemctl daemon-reload
sudo systemctl restart apt-daily.timer apt-daily-upgrade.timer
systemctl list-timers 'apt-daily*'
The empty OnCalendar= line clears the default schedule before we set ours. RandomizedDelaySec adds a small random delay so that many servers do not hit the mirrors at the same second. The timer list should now show the next runs around 01:00/13:00 and 03:00.
Debian vs Ubuntu reminder: on Ubuntu, /run/reboot-required is created by update-notifier-common after kernel updates and some core libraries, and /run/reboot-required.pkgs tells you which packages asked for it. On Debian, the flag comes from the kernel hook installed with unattended-upgrades, so it appears mainly after kernel updates and the .pkgs file may be missing. You can check by hand any time:
ls -l /run/reboot-required* 2>/dev/null || echo "no reboot needed"
uname -r
uname -r shows the kernel that is running right now. For services that still use old library versions in memory, needrestart helps. It is preinstalled on Ubuntu; on Debian install it with sudo apt install -y needrestart, then list what would need a restart without restarting anything:
sudo needrestart -r l
Inside an unprivileged LXC container, the kernel belongs to the host, not to your container, so there is no kernel package to update and the reboot flag rarely appears. If it does, “reboot” only restarts your container, which takes a few seconds. The host kernel is patched by your provider. On a KVM VPS, your server has its own kernel and the steps above apply in full.
If you would rather not reboot automatically, leave Automatic-Reboot set to "false". The report email then gets a [reboot required] tag in its subject, and you can reboot by hand in your own maintenance window.
Step 7: Test with a dry run and read the logs
Do not wait until tomorrow to find out whether it works. A dry run goes through the whole decision process without installing anything:
sudo unattended-upgrade --dry-run --debug
The output is long. Look for lines like these:
Starting unattended upgrades script
Allowed origins are: origin=Debian,codename=trixie,label=Debian, origin=Debian,codename=trixie,label=Debian-Security, ...
Packages that will be upgraded: libssl3t64 openssl
If nothing is pending you will see No packages found that can be upgraded unattended and no pending auto-removals, which is also a success. If you want to apply pending updates right away instead of waiting for the night, run it for real with sudo unattended-upgrade -v.
From now on, these are the places to look:
sudo tail -n 40 /var/log/unattended-upgrades/unattended-upgrades.log
sudo tail -n 40 /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
grep -A4 'Start-Date' /var/log/apt/history.log | tail -n 20
journalctl -u apt-daily-upgrade.service --since "3 days ago"
The first file is the summary of each run (what was allowed, what was installed, whether a reboot was scheduled). The second has the detailed package installation output. history.log records every apt operation, manual or automatic. The journal shows when the timer actually fired. When a reboot is scheduled, you will see a line similar to Found /var/run/reboot-required, rebooting in the first log, and cat /run/systemd/shutdown/scheduled shows the pending reboot time.
Troubleshooting and common mistakes
“Could not get lock /var/lib/dpkg/lock-frontend”. You ran apt while unattended-upgrades was working. Wait a few minutes and try again. Never delete lock files to “fix” this: interrupting dpkg mid-install is how packages end up broken.
Nothing is ever upgraded. Run apt-config dump APT::Periodic and check that Unattended-Upgrade "1" is there. On Ubuntu there is also /etc/apt/apt.conf.d/10periodic; whichever file is read last wins, and the dump shows the final result. Also check systemctl is-enabled apt-daily-upgrade.timer and run the dry run from step 7 to see which origins are allowed.
A package is “kept back” with a conffile message. The log says something like “Package X has conffile prompt and needs to be upgraded manually”. You edited a configuration file that the new version also changed, and unattended-upgrades refuses to guess. Run sudo apt upgrade yourself and decide.
Ubuntu holds some updates back. Ubuntu rolls out some updates in phases to a percentage of machines at a time. A package “deferred due to phasing” will arrive in a few days; this is normal.
No emails arrive. Test with the mail command from step 5, then read msmtp’s messages with journalctl -t msmtp -n 20. On Debian 13 there is no /var/log/syslog by default, so the journal is the place to look; on Ubuntu you can also use grep msmtp /var/log/syslog. Wrong password, wrong port and a sender address the provider does not accept are the usual causes.
The server never reboots. Check that Automatic-Reboot "true" appears in apt-config dump, that the reboot flag actually exists, and that the time zone is what you expect. Remember that on Debian only kernel updates create the flag.
Packages from Docker or other vendors are not updated. That is by design. If you trust a third-party repository, find its origin and label with apt-cache policy and add a matching line to your local file, for example Unattended-Upgrade::Origins-Pattern { "origin=Docker,label=Docker CE"; };. Think twice: a major version jump from a vendor can change behavior overnight.
Security notes
- Protect the SMTP password. Keep
/etc/msmtprcowned by root with mode600, and use a dedicated SMTP account or an app password, not your main mailbox password. - Stay on security origins. Do not add
-proposed,-backportsor random PPAs to the allowed list. The point is predictable, minimal fixes. - Keep backups anyway. Automatic updates reduce risk, they do not remove it. A daily backup, stored off the server, is your safety net for any change, automatic or manual.
- Monitor the reboot. If you enable automatic reboots, have an external uptime check on your site or SSH port, so you hear about it if the server does not come back after 04:00.
- Do not disable the timers “temporarily”. A disabled timer is easy to forget, and a forgotten server is exactly what bots look for. If you need a pause, use
apt-mark holdon specific packages instead. - Patching is one layer. Combine it with a firewall that only opens the ports you use, SSH key authentication and disabled root login over SSH.
So, should you let your server patch itself?
For almost every VPS: yes. With about 15 minutes of setup, unattended-upgrades installs security fixes within hours of their release, emails you when something changes, cleans old kernels, and reboots at 04:00 only when a new kernel is waiting. The few cases where you want manual control (a database, a pinned application stack) are handled with holds, not by switching the whole mechanism off.
If you want to try it on a clean machine, a VPS with us runs Debian 13 or Ubuntu 24.04 and is ready in minutes. And if you would rather have someone else own patching, reboots and monitoring end to end, our server administration team can set it up and watch it for you.
FAQ
Does unattended-upgrades upgrade Debian 13 to Debian 14 or Ubuntu 24.04 to the next LTS?
No. It only installs updates within your current release. Moving to a new major version is a separate, manual process that you should plan and test.
Will my website go down during updates?
Usually not in any noticeable way. A service like Nginx or PHP-FPM may restart for a second or two when its package is upgraded. The only real interruption is the reboot, which is why we schedule it at 04:00 and only when a kernel update needs it.
Is it safe to enable automatic reboots on a production server?
For a single VPS with a short boot time, yes, as long as your services start automatically at boot (Docker containers with a restart policy, systemd services that are enabled) and you have an uptime check. For setups where a reboot must be coordinated across several servers, keep Automatic-Reboot off and reboot in a planned window.
Does it work inside an LXC container?
Yes, for all regular packages. The kernel is shared with the host, so there are no kernel updates inside the container and reboots are rare and fast. The host kernel is maintained by the provider.
How do I turn it off if I need to?
Set both values in /etc/apt/apt.conf.d/20auto-upgrades to "0", or run sudo dpkg-reconfigure -plow unattended-upgrades and answer No. Remember to turn it back on.