How to Rebuild the Linux Kernel: Complete Compilation and Installation Guide

how to rebuilding the kernel in Linux

Rebuilding a kernel from source sounds like something only kernel developers do, but I’ve done it plenty of times for far more mundane reasons — enabling a driver my distribution left out, patching in a fix ahead of an official release, or just tuning things for a specific machine. This guide walks through the whole process, from raw source to a bootable, working custom kernel, including all the ways I’ve personally gotten it wrong along the way.

Before You Start: What You’ll Need

Compiling a kernel needs a proper build toolchain and a set of development libraries. On Debian/Ubuntu:

sudo apt update
sudo apt install build-essential libncurses-dev bison flex libssl-dev \
    libelf-dev bc dwarves fakeroot

On RHEL/Fedora/CentOS:

sudo dnf install gcc gcc-c++ make ncurses-devel bison flex openssl-devel \
    elfutils-libelf-devel bc dwarves rpm-build

You’ll also want a reasonable amount of free disk space — a full kernel source tree plus build artifacts can easily use 15-20GB, and plenty of RAM helps a lot since the build can be parallelized heavily.

Step 1: Obtain the Kernel Source

I generally get source one of two ways, depending on the goal.

Vanilla upstream source, when I want the latest mainline kernel or a specific version straight from the maintainers:

cd /usr/src
sudo wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.9.tar.xz
sudo tar -xJf linux-6.9.tar.xz
cd linux-6.9

Distribution source package, when I want to stay closer to what my distro ships, plus any distro-specific patches:

# Debian/Ubuntu
apt-get source linux-image-$(uname -r)

# RHEL/Fedora - fetch the source RPM
dnf download --source kernel
rpm -ivh kernel-*.src.rpm

I generally recommend starting with a distribution source package if your goal is a small, targeted modification, and going vanilla upstream if you specifically need the newest kernel features not yet backported to your distro’s release.

Step 2: Configure the Kernel

This deserves its own detailed treatment (which I cover fully in my kernel configuration guide), but briefly, before building you need a valid .config:

cp /boot/config-$(uname -r) .config
make olddefconfig

Or interactively adjust it:

make menuconfig

I always run make olddefconfig at minimum, even if I don’t plan to change anything, just to make sure any newly introduced kernel options since my base config’s version get sane default answers rather than being left undefined.

Step 3: Compile the Kernel

This is the actual compilation step, and it’s where parallelism matters most for how long you’ll be waiting around.

make -j$(nproc)

The -j flag controls how many compilation jobs run in parallel. $(nproc) automatically detects the number of available CPU cores. On a modest 4-core machine, a full kernel build can take anywhere from 20 minutes to over an hour depending on how much is enabled; on a beefy multi-core build server, it can be under 10 minutes.

You can also build just the kernel image without modules first, useful for quick sanity checks:

make -j$(nproc) bzImage

Building Modules Separately

If you only changed something that affects modules, you can rebuild just those rather than the whole tree:

make -j$(nproc) modules

Step 4: Install Modules

Once the build finishes successfully, install the compiled modules into /lib/modules/<version>/:

sudo make modules_install

This copies everything into the correct versioned directory and runs depmod automatically to regenerate the module dependency database.

Step 5: Install the Kernel Image

sudo make install

On most distributions using GRUB, this step also handles copying the kernel image into /boot, generating the corresponding initrd/initramfs (invoking the distribution’s hook scripts, like update-initramfs on Debian/Ubuntu or dracut on RHEL/Fedora), and updating the bootloader configuration automatically.

If it doesn’t handle initrd generation automatically on your setup, do it manually:

# Debian/Ubuntu
sudo update-initramfs -c -k 6.9.0

# RHEL/Fedora
sudo dracut --force /boot/initramfs-6.9.0.img 6.9.0

Step 6: Update the Bootloader

For GRUB2-based systems:

# Debian/Ubuntu
sudo update-grub

# RHEL/Fedora/CentOS
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

I always inspect the generated grub.cfg afterward to confirm the new kernel entry actually appears with the correct paths:

grep -A2 "menuentry 'Advanced" /boot/grub2/grub.cfg | grep -i "6.9.0"

Step 7: Reboot and Verify

sudo reboot

After reboot, confirm you’re actually running the new kernel:

uname -r

And check dmesg for anything unexpected during the new kernel’s boot:

dmesg | less

Building a Distribution Package Instead of Raw Install

I strongly prefer this approach whenever I’m building kernels I intend to keep around long-term, roll back easily, or deploy to multiple machines, because it integrates properly with the package manager’s tracking, upgrade, and removal mechanisms rather than leaving loose files scattered around /boot and /lib/modules.

Debian/Ubuntu: Building a .deb Package

make -j$(nproc) bindeb-pkg

This produces .deb files in the parent directory, which you then install normally:

sudo dpkg -i ../linux-image-6.9.0_6.9.0-1_amd64.deb
sudo dpkg -i ../linux-headers-6.9.0_6.9.0-1_amd64.deb

Because this goes through dpkg, uninstalling later is as clean as sudo dpkg -r linux-image-6.9.0, and the postinst/postrm scripts correctly handle initrd regeneration and GRUB updates automatically.

RHEL/Fedora: Building an RPM Package

make -j$(nproc) rpm-pkg

Or using the more traditional full spec-file-driven approach:

rpmbuild -ba kernel.spec
sudo rpm -ivh ~/rpmbuild/RPMS/x86_64/kernel-6.9.0-1.x86_64.rpm

Cross-Compiling for a Different Architecture

Occasionally I need to build a kernel for a different target architecture than the machine I’m building on — commonly for ARM-based embedded boards. This requires a cross-compilation toolchain and setting ARCH and CROSS_COMPILE:

sudo apt install gcc-aarch64-linux-gnu

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)

The resulting image and modules then need to be transferred to the target board’s filesystem manually, since make install on the build host would install into the host’s own /boot, not the target’s.

Verifying Your New Kernel

A few checks I always run right after switching to a new custom kernel:

# Confirm running version
uname -r

# Confirm loaded modules look sane
lsmod | head -20

# Check for any boot-time errors or warnings
dmesg | grep -i -E "error|fail|warn"

# Confirm expected hardware is detected
lspci -k
lsusb

Troubleshooting Common Build and Boot Problems

Build fails with missing header errors — almost always a missing development package. Read the actual error message; it usually names the missing header file, which maps directly to a -dev/-devel package you’re missing.

“No rule to make target” errors — often means .config references a feature not properly resolved; rerun make olddefconfig before building again.

System won’t boot into the new kernel — dropped to emergency shell — this is almost always a missing driver for your root filesystem or storage controller, either not built into the kernel or missing from the initrd. Regenerate the initrd explicitly including the needed module, as covered in my initrd/mkinitrd guide.

New kernel boots but wireless/graphics/etc. stopped working — check whether the relevant driver was accidentally disabled during configuration, or whether it needs an out-of-tree module (like proprietary GPU drivers) that must be rebuilt separately against the new kernel headers using tools like DKMS.

Build takes far too long — verify you’re actually using -j$(nproc) and not building single-threaded by accident; also check that ccache isn’t misconfigured if you’re using it, since a broken cache can sometimes force full rebuilds every time.

Using DKMS for Third-Party Modules

If you rely on out-of-tree kernel modules (NVIDIA proprietary drivers, VirtualBox host modules, some Wi-Fi drivers), install DKMS so those modules automatically rebuild against your new custom kernel without manual intervention:

sudo apt install dkms
dkms status

After installing a new kernel, DKMS-managed modules typically rebuild automatically as part of the kernel package’s post-install hooks, assuming you built and installed via the .deb/.rpm packaging route rather than a raw make install.

Performance and Optimization Notes

  • Use ccache to dramatically speed up repeated builds during iterative development: export CC="ccache gcc" before building.
  • Trim unused config options aggressively (see my kernel configuration guide) — smaller kernels compile faster and boot faster.
  • Use make -j$(nproc) always; a single-threaded kernel build on modern multi-core hardware is needlessly slow.
  • Consider LOCALVERSION in your config to clearly tag custom builds (e.g., -myserver), which makes it trivial to distinguish your custom kernel from stock ones in uname -r output and GRUB menus.

Rolling Back Safely

Because GRUB keeps previous kernel entries by default (assuming you didn’t remove the old kernel package), rolling back from a bad custom kernel is usually just a matter of selecting the previous entry from the GRUB boot menu at startup, or setting the default boot entry back:

sudo grub2-set-default 0
sudo update-grub    # or grub2-mkconfig depending on distro

I never remove a known-working kernel until I’ve confirmed the new one is stable under real usage for at least a few days.

Signing Modules and Secure Boot Considerations

On systems with UEFI Secure Boot enabled, an unsigned custom kernel or unsigned custom modules will be refused by the firmware or rejected by the kernel’s module loader at runtime. If you’re building on a machine with Secure Boot active, you have a few options: disable Secure Boot in firmware settings (simplest, but reduces a layer of boot integrity protection), or generate and enroll your own Machine Owner Key (MOK) to sign your custom kernel and modules:

openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 3650 -subj "/CN=My Custom Kernel Signing Key/"
sudo mokutil --import MOK.der

After a reboot, the MOK enrollment screen (mokutil‘s companion firmware UI) prompts you to confirm the new key. Once enrolled, you sign your built kernel and modules with the corresponding private key so Secure Boot accepts them at boot time. I’ve hit this exact issue more than once on modern laptops and workstations shipped with Secure Boot on by default, and it’s worth checking mokutil --sb-state before you even start a custom build, so it doesn’t surprise you after a long compile.

Keeping Multiple Kernels Installed Side by Side

I always keep at least one previous known-good kernel installed alongside any custom build, specifically as a safety net. Both .deb and .rpm packaging naturally support this, since each kernel version installs under its own versioned directory in /lib/modules/ and gets its own GRUB menu entry rather than overwriting the previous one. I only remove an old kernel package once I’m confident the new one has been stable in real use for a reasonable stretch of time — a week of normal workload is my personal minimum bar before I consider cleaning up.

# List installed kernel packages (Debian/Ubuntu)
dpkg -l | grep linux-image

# List installed kernel packages (RHEL/Fedora)
rpm -qa | grep kernel

Testing a New Kernel Safely Before Committing

Rather than rebooting straight into a brand-new custom kernel on a production machine, I test it first in a disposable environment when at all possible — a virtual machine snapshot, or a spare piece of hardware matching the target closely enough to catch obvious problems. For genuinely critical production systems, I schedule the first real-world boot of a new custom kernel during a planned maintenance window with console/IPMI access available, specifically so I’m not locked out if something goes wrong with networking drivers or similar early-boot-critical functionality.

Compatibility Across Distributions

The underlying make targets (bzImage, modules, modules_install, install) are universal across all Linux distributions since they come from the upstream kernel’s own Makefile, not anything distribution-specific. What differs is the packaging convenience layer on top — Debian-family systems favor bindeb-pkg, RHEL-family systems favor rpm-pkg or full rpmbuild spec files, and Arch Linux uses its own PKGBUILD mechanism through the linux package group in the AUR or its own build scripts. The compile-configure-install fundamentals I’ve walked through here apply identically regardless of which packaging convention you use at the end.

Summary

Rebuilding the Linux kernel is really a five-act process: get the source, configure it, compile it, install the modules and image, then update the bootloader. Each step has failure modes, but they’re all diagnosable once you understand what each stage is actually responsible for. I strongly recommend building distribution packages (.deb/.rpm) rather than raw make install for anything you intend to run long-term, since it gives you clean upgrade and rollback paths through your normal package manager. Once you’ve done this once successfully end to end, it stops being intimidating and becomes just another tool in your systems administration kit.

References

  • The Linux Kernel documentation: Documentation/admin-guide/README.rst
  • Documentation/kbuild/ in the kernel source tree
  • kernel.org release archive and changelogs
  • Debian Kernel Handbook
  • Red Hat documentation on building custom kernels with rpmbuild

Total
1
Shares

Leave a Reply

Previous Post
wget command in Linux and it perimeters

wget Command in Linux: Complete Guide to File Downloading and Parameters

Next Post
how to configure the kernel in Linux

How to Configure the Linux Kernel: Complete Customization and Optimization Guide

Related Posts