Cleaning Up Old Kernels on Ubuntu
A simple bash script to safely remove old kernels on Ubuntu when apt autoremove doesn't get the job done.
If you've been running an Ubuntu server for a while, you've probably noticed /boot filling up with old kernel images. Ubuntu installs new kernels with each update but doesn't always clean up the old ones.
Why Not Just Use apt autoremove?
The official answer is sudo apt autoremove --purge. In practice, it often leaves old kernels behind. This happens because:
- Manually installed kernels — if a kernel was installed via
apt install linux-image-*, apt considers it explicitly requested and won't auto-remove it. - Stale autoremoval config — Ubuntu maintains a protection list in
/etc/apt/apt.conf.d/01autoremove-kernelsthat can get out of sync after partial upgrades or version pinning. - Held or pinned packages — anything marked
holdor pinned in apt preferences is excluded from autoremoval, even if it's no longer needed.
When autoremove isn't cutting it, a targeted script can take care of the rest.
The Script
1#!/bin/bash2# Remove old kernels on Ubuntu/Debian systems3# Run without arguments for a dry run4# Run with "exec" argument to actually remove old kernels5# Example: sudo ./remove-old-kernels.sh exec67set -euo pipefail89# Ensure we're running on a Debian-based system10if ! command -v dpkg &>/dev/null; then11 echo "Error: This script requires dpkg (Debian/Ubuntu systems only)."12 exit 113fi1415CURRENT_KERNEL=$(uname -r)16CURRENT_VERSION=$(echo "$CURRENT_KERNEL" | sed 's/-[a-z].*$//')17echo "Currently running kernel: $CURRENT_KERNEL"1819# Get only installed (ii) kernel-related packages, excluding the running kernel20# and meta-packages (linux-image-generic, linux-headers-generic, etc.)21OLD_KERNELS=$(22 dpkg --list |23 awk '/^ii\s+(linux-image|linux-headers|linux-modules)/ { print $2 }' |24 grep -v "$CURRENT_VERSION" |25 grep -E '[0-9]+\.[0-9]+\.[0-9]+' || true26)2728if [ -z "$OLD_KERNELS" ]; then29 echo "No old kernels found to remove."30 exit 031fi3233echo ""34echo "Old kernels to be removed:"35echo "$OLD_KERNELS"36echo ""3738if [ "${1:-}" == "exec" ]; then39 if [ "$EUID" -ne 0 ]; then40 echo "Error: Must run as root. Use: sudo $0 exec"41 exit 142 fi43 apt purge -y $OLD_KERNELS44 echo ""45 echo "Done. Running 'update-grub' to refresh boot menu..."46 update-grub47 echo "Old kernels removed successfully."48else49 echo "Dry run complete. To remove these kernels, run:"50 echo " sudo $0 exec"51fi1#!/bin/bash2# Remove old kernels on Ubuntu/Debian systems3# Run without arguments for a dry run4# Run with "exec" argument to actually remove old kernels5# Example: sudo ./remove-old-kernels.sh exec67set -euo pipefail89# Ensure we're running on a Debian-based system10if ! command -v dpkg &>/dev/null; then11 echo "Error: This script requires dpkg (Debian/Ubuntu systems only)."12 exit 113fi1415CURRENT_KERNEL=$(uname -r)16CURRENT_VERSION=$(echo "$CURRENT_KERNEL" | sed 's/-[a-z].*$//')17echo "Currently running kernel: $CURRENT_KERNEL"1819# Get only installed (ii) kernel-related packages, excluding the running kernel20# and meta-packages (linux-image-generic, linux-headers-generic, etc.)21OLD_KERNELS=$(22 dpkg --list |23 awk '/^ii\s+(linux-image|linux-headers|linux-modules)/ { print $2 }' |24 grep -v "$CURRENT_VERSION" |25 grep -E '[0-9]+\.[0-9]+\.[0-9]+' || true26)2728if [ -z "$OLD_KERNELS" ]; then29 echo "No old kernels found to remove."30 exit 031fi3233echo ""34echo "Old kernels to be removed:"35echo "$OLD_KERNELS"36echo ""3738if [ "${1:-}" == "exec" ]; then39 if [ "$EUID" -ne 0 ]; then40 echo "Error: Must run as root. Use: sudo $0 exec"41 exit 142 fi43 apt purge -y $OLD_KERNELS44 echo ""45 echo "Done. Running 'update-grub' to refresh boot menu..."46 update-grub47 echo "Old kernels removed successfully."48else49 echo "Dry run complete. To remove these kernels, run:"50 echo " sudo $0 exec"51fi
How It Works
The script has two modes: a dry run (default) that shows what would be removed, and an exec mode that actually purges the packages.
Finding old kernels:
1dpkg --list |2 awk '/^ii\s+(linux-image|linux-headers|linux-modules)/ { print $2 }' |3 grep -v "$CURRENT_VERSION" |4 grep -E '[0-9]+\.[0-9]+\.[0-9]+'1dpkg --list |2 awk '/^ii\s+(linux-image|linux-headers|linux-modules)/ { print $2 }' |3 grep -v "$CURRENT_VERSION" |4 grep -E '[0-9]+\.[0-9]+\.[0-9]+'
This pipeline does a few important things:
awk '/^ii/'— only matches installed packages.dpkg --listalso shows removed and half-configured packages; we don't want those.grep -v "$CURRENT_VERSION"— excludes anything matching the running kernel's base version (e.g.,6.8.0-106). This protects both the flavor-specific packages likelinux-image-6.8.0-106-genericand the common packages likelinux-headers-6.8.0-106that the running kernel depends on.grep -E '[0-9]+\.[0-9]+\.[0-9]+'— only matches versioned packages. This is the key safety measure: it excludes meta-packages likelinux-image-genericandlinux-headers-generic. Removing those would break future kernel updates viaapt upgrade.
After purging, the script runs update-grub to refresh the boot menu so removed kernels no longer appear at startup.
Usage
First, do a dry run to review what will be removed:
1sudo bash remove-old-kernels.sh1sudo bash remove-old-kernels.sh
Example output:
1Currently running kernel: 6.8.0-106-generic23Old kernels to be removed:4linux-headers-6.8.0-1015linux-headers-6.8.0-101-generic6linux-image-6.8.0-101-generic7linux-modules-6.8.0-101-generic8linux-modules-extra-6.8.0-101-generic1Currently running kernel: 6.8.0-106-generic23Old kernels to be removed:4linux-headers-6.8.0-1015linux-headers-6.8.0-101-generic6linux-image-6.8.0-101-generic7linux-modules-6.8.0-101-generic8linux-modules-extra-6.8.0-101-generic
If the list looks right, run it for real:
1sudo bash remove-old-kernels.sh exec1sudo bash remove-old-kernels.sh exec
Things to Keep in Mind
- Always do the dry run first. Review the package list before removing anything.
- This is for Ubuntu/Debian only. Fedora, Arch, and others handle kernel packaging differently.
- Try
apt autoremove --purgefirst. Use this script for what autoremove misses. - Tested on Ubuntu 24.04 LTS with kernel
6.8.0-106-generic.