Server hardware interior showing drives and cable management in a data center setting
Kernel Updates
William  

Why Apt Autoremove Leaves Old Kernels Behind

Start with sudo apt autoremove. It clears the kernels the package manager already flagged as obsolete, and on a healthy system that is the whole job. But it stops there on purpose, so if /boot is still full or old versions linger in dpkg --list, autoremove is not broken. It just refuses to touch anything it did not install automatically. From there you check uname -r to see what is running, list every kernel with dpkg --list | grep linux-image, and purge the leftovers by hand. Understanding why autoremove skipped them matters more than the purge command itself.

Why apt autoremove leaves some kernels behind

apt autoremove only removes packages marked as automatically installed and no longer needed. A kernel earns that mark when it arrived as a dependency of linux-generic and a newer one has replaced it. If you ever installed a kernel by hand, or ran apt install linux-image-... directly, that package is flagged manual, and autoremove will not go near it.

That is the first gap. The second is pinning and holds. Run apt-mark showhold and apt-mark showmanual | grep linux to see what is locked in place. A held kernel stays put no matter how many newer ones land on top of it.

The current running kernel is also protected, which is correct. Removing the kernel you booted from is how you turn a cleanup into a rescue-disk afternoon. So autoremove leaves at least the running one alone by design, along with anything you touched manually.

Here is what happens in practice: someone follows a driver guide, installs a specific kernel to fix a wifi chip, and forgets it. Months later /boot is tight, autoremove says nothing to do, and the old manual kernel sits there. Check the manual flag before you blame the tool.

How do you tell which kernels are installed versus running?

Run these two and read them side by side:

uname -r
dpkg --list | grep linux-image

uname -r prints the exact version you are booted into right now, for example 5.15.0-91-generic. That is the one line you never remove. The dpkg --list output is every kernel image the package database knows about, one per row, with the version buried in the name.

The ii at the start of a dpkg row means installed and configured. An rc prefix means the package is removed but its config files remain, which is harmless clutter, not a real kernel eating /boot. Learn to skip the rc rows or you will chase ghosts.

To match what actually sits in the boot partition, list it directly:

ls -lh /boot

Each kernel drops a vmlinuz, an initrd.img, a config, and a System.map, all tagged with the same version string. Cross-reference those version strings against uname -r. Anything in /boot that is not the running version and not your chosen fallback is a candidate for removal. Base the decision on that comparison rather than a hunch about which one is old.

Removing a specific kernel by hand

When autoremove refuses and you know the version you want gone, purge it explicitly. Say you are done with 5.15.0-84-generic:

sudo apt purge linux-image-5.15.0-84-generic linux-headers-5.15.0-84-generic linux-modules-5.15.0-84-generic

Three package families make up one kernel: the image, the headers, and the modules. Remove the image and leave the headers and you have freed almost nothing while the row still shows in dpkg. Tab-completion after linux-image- lists the versions apt can see, which saves you a typo that purges the wrong one.

Watch the output before you hit enter. apt prints exactly what it will remove, and the running kernel or your fallback must not appear in that list. If it does, cancel. A stray linux-generic or linux-image-generic in the removal set means apt is about to pull your meta-package, so back out and name the versioned packages instead.

Each kernel version you clear frees roughly 50 to 100 MB, most of it in /boot and /usr/lib/modules. After the purge, confirm with dpkg --list | grep linux-image and ls /boot. The version should be gone from both. If modules linger under /usr/lib/modules, my step-by-step guide to removing unused kernel modules covers the cleanup that apt sometimes skips.

How many kernels to keep for fallback

Keep 2 or 3 kernel versions, which is what most Ubuntu systems retain, and stop there. You want the one you are running plus at least one known-good previous kernel. When an update kills your wifi or the machine refuses to boot, you pick the older entry from the GRUB menu and you are working again while you debug the new one.

That fallback is the entire reason not to purge down to a single kernel. I have watched people clean so aggressively that a bad kernel update left them nothing to boot but the broken one. If you want the recovery routine itself, I wrote it up in rolling back a Linux kernel update.

Match the count to the machine:

MachineWhat I keepWhy
Server with a tight /bootRunning plus one previousMinimal fallback, least churn
Normal desktopRunning plus two previousRoom to skip a bad release
Box for testing new kernelsThe full 2 or 3Bisecting regressions needs history

Anything past that is hoarding. Nobody boots a kernel that many releases behind on purpose. On Ubuntu 20.04 LTS and later, the automatic cleanup already aims at this range, so let apt hold the line rather than fight it.

Why is /boot still full even after removing kernels?

Check what is actually there before you resize anything:

df -h /boot
sudo du -sh /boot/*

df tells you how full the partition is; du tells you which files are eating it. The usual culprit is a leftover initrd.img from a kernel whose purge died halfway, so the version shows gone in dpkg but the fat initrd file still sits in /boot. Delete those stragglers by name only after you confirm they belong to a version you already purged.

A half-finished apt run is the other common cause. If a previous cleanup failed because /boot filled mid-update, dpkg is wedged. Run sudo dpkg --configure -a to finish the interrupted work, then sudo apt --fix-broken install, and try the autoremove again with space now free.

When the partition itself is the problem

Older installs, especially upgraded ones, sometimes carved out a /boot too small for how kernels grew. If it is genuinely full of current, valid kernels and nothing stale, that is a sizing problem, not a cleanup one. Lower your retention to the running kernel plus one, which usually buys enough room without a repartition. Resizing /boot is real work with real risk, so I reach for it last, after confirming the space is spent on kernels I actually need.

Update prompts that list kernels you already removed

A prompt naming a kernel you swear you deleted is usually stale metadata, not a real leftover. The desktop update notifier and the "restart required" banner cache their view of the system and do not always refresh the instant a purge finishes. Confirm the truth from the package database, not the popup:

dpkg --list | grep linux-image

If the version is not in that list with an ii, it is gone, and the prompt is cosmetic. Refresh apt's own picture with sudo apt update, and the notifier catches up on its next cycle or the next login.

The real leftover looks different. It shows as an installed ii row in dpkg, or apt keeps offering to configure it because a held or half-configured package is stuck. That is when sudo apt --fix-broken install and a targeted purge earn their keep. Tell the two apart by reading dpkg, because the prompt alone will lie to you either way.

If you want to stop reacting to these prompts and put updates on a schedule instead, I covered that in scheduling Linux kernel updates. The mechanics that generate these menu entries live in the GNU GRUB bootloader, which reads /boot at every update to rebuild its list.

FAQ

Does removing old kernels ever break the running system?
Not if you leave the booted kernel and one fallback in place. The danger is purging a meta-package like linux-generic, which drags out packages you need. Read the removal list apt prints and cancel if a generic meta-package or the running version appears in it.

How do I stop Ubuntu from keeping so many kernels automatically?
The automatic cleanup already trims toward two or three versions on its own. If you want fewer, run sudo apt autoremove --purge after updates so config files go too, and avoid installing specific kernels by hand, since manual installs never get auto-removed.

What is the difference between an ii and an rc kernel in dpkg?
ii means the package is installed and taking up space in /boot. rc means the binary is already removed and only leftover config files remain, which cost almost nothing. Clear the rc remnants with sudo apt purge if the tidy output bothers you, but they are not why /boot is full.

Can I remove kernel headers if I do not compile anything?
Yes, on a machine that never builds modules or DKMS drivers, the linux-headers-* packages are safe to purge. Some out-of-tree drivers rebuild against headers on each kernel bump, though, so if your graphics or wifi driver uses DKMS, keep the headers for your current and fallback kernels.

How do I confirm a purge actually freed space?
Run df -h /boot before and after. Each kernel version you clear returns roughly 50 to 100 MB, most of it in the boot partition. If the number does not move, a leftover initrd.img survived the purge, so check ls -lh /boot and remove the stale file by name.