Automating Full Upgrades and Reboots on Ubuntu Servers

sysadmin ubuntu automation security

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:

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


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.