Keeping servers patched is one of those chores that feels simple — until you realize you’ve been putting it off for weeks. The usual “apt update && apt upgrade && reboot” dance works fine when you remember to log in, but on real infrastructure it quickly becomes drift, delay, and risk.
The good news: Ubuntu has all the pieces to automate this. With a little tuning, your servers can:
- Apply updates and upgrades daily
- Clean up unneeded packages
- Reboot themselves when a kernel or libc update requires it
No more SSH sessions just to mash apt upgrade
.
Why the Default Isn’t Enough
Ubuntu ships with unattended-upgrades, but out of the box it only installs security patches. That’s safe for desktop users, but it means regular bug fixes and feature updates pile up. If you’re running ephemeral Docker hosts or VM “cattle,” you actually want everything current — security and updates — and you don’t care if the box reboots at night.
One Script to Handle It
Here’s a script you can drop onto any Ubuntu LTS server. Run it once with sudo, and it sets up unattended upgrades, cleanup, and automatic reboots.
#!/usr/bin/env bash
# ubuntu-unattended-full.sh
# Sets up unattended full upgrades (+cleanup) and reboots on Ubuntu LTS hosts.
# Safe to re-run. Assumes systemd (standard on Ubuntu servers).
set -euo pipefail
require_root() {
if [[ $EUID -ne 0 ]]; then
echo "Run as root (use sudo)." >&2
exit 1
fi
}
detect_distro() {
source /etc/os-release
DISTRO_ID="${ID^}"
CODENAME="${VERSION_CODENAME:-$(lsb_release -sc 2>/dev/null || true)}"
if [[ -z "${CODENAME}" || "${DISTRO_ID}" != "Ubuntu" ]]; then
echo "This script is intended for Ubuntu. Detected: ID='${ID}', CODENAME='${CODENAME}'" >&2
exit 1
fi
}
backup_file() {
local f="$1"
if [[ -f "$f" ]]; then
cp -a "$f" "${f}.bak.$(date +%Y%m%d%H%M%S)"
fi
}
install_packages() {
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y unattended-upgrades apt-listchanges
}
write_50unattended() {
local f="/etc/apt/apt.conf.d/50unattended-upgrades"
backup_file "$f"
cat >"$f" <<EOF
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_id}:\${distro_codename}-updates";
};
// Docker CE stable
Unattended-Upgrade::Origins-Pattern {
"o=Docker,l=Docker CE,a=\${distro_codename},c=stable";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
EOF
}
write_20auto() {
local f="/etc/apt/apt.conf.d/20auto-upgrades"
backup_file "$f"
cat >"$f" <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Autoremove "1";
EOF
}
enable_timers() {
systemctl enable --now apt-daily.timer apt-daily-upgrade.timer >/dev/null 2>&1 || true
}
kick_once_now() {
echo "Running unattended-upgrade once now..."
unattended-upgrade -v || true
if [[ -f /var/run/reboot-required ]]; then
echo "Reboot required (will occur at configured time window)."
cat /var/run/reboot-required.pkgs || true
else
echo "No reboot required after initial run."
fi
}
main() {
require_root
detect_distro
install_packages
write_50unattended
write_20auto
enable_timers
kick_once_now
echo "Setup complete — automatic daily upgrades and reboots enabled."
}
main "$@"
Save as ubuntu-unattended-full.sh
, make it executable, and run it:
chmod +x ubuntu-unattended-full.sh
sudo ./ubuntu-unattended-full.sh
That’s it — the box will keep itself patched and reboot if it needs to.
What It Does
- Updates & upgrades: pulls from both Ubuntu updates and Docker CE stable.
- Cleanup: runs autoremove and autoclean to keep the system tidy.
- Reboots: if a kernel or libc update requires it, the server reboots at 3 AM local time.
Bonus Tip: Full Purge Cleanup
By default, the script runs autoremove
and autoclean
, which clears unused dependencies and old package files. That keeps disk usage under control.
If you want to go further and wipe out leftover configuration files as well, you can add a small cron job that runs autoremove --purge
. It doesn’t save much disk space, but it does keep the system spotless:
sudo tee /etc/cron.weekly/apt-purge-clean >/dev/null <<'EOF'
#!/bin/sh
set -e
/usr/bin/apt-get -y autoremove --purge
/usr/bin/apt-get -y autoclean
EOF
sudo chmod +x /etc/cron.weekly/apt-purge-clean
That will run once a week, removing unused packages and their config files. Perfect if you like your servers squeaky clean.
Final Word
This setup keeps your Ubuntu servers patched, clean, and consistent with zero ongoing effort. Whether you’ve got five hosts or fifty, it’s one less thing to think about — and one less reason to log in at 2 AM.