Linux··6 min

Compiling and Installing a Custom Kernel

Compiling a custom kernel is the deepest level of Linux. This post covers the full process — getting the source, configuring with menuconfig, building, generating an initramfs, updating GRUB, and booting your own kernel.

Compiling a custom kernel is the deepest level of Linux. Most people never need to do it — distribution kernels are well-configured and heavily tested. But knowing how gives you a complete picture of what the kernel actually is, lets you enable features not in distribution kernels, apply patches, and strip down the kernel for embedded or specialised systems.

This post covers the full process from source to boot.

Why compile a custom kernel?

  • Enable features disabled in the distribution kernel
  • Apply a patch not yet merged upstream (realtime patch, hardware support, security fix)
  • Strip unused drivers for a smaller, faster kernel (embedded systems, VMs)
  • Learn how the kernel configuration works
  • Test a newer kernel version before it is packaged

Prerequisites

Install the build dependencies:

# Debian/Ubuntu
sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev bc dwarves
 
# Fedora/RHEL
sudo dnf install gcc make ncurses-devel bison flex openssl-devel elfutils-libelf-devel bc dwarves

You will need at least 20GB of free disk space and expect the build to take 30 minutes to several hours depending on your CPU.

Getting the kernel source

From kernel.org

wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.30.tar.xz
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.30.tar.sign
xz -d linux-6.6.30.tar.xz
tar xf linux-6.6.30.tar
cd linux-6.6.30

Verify the signature

gpg --locate-keys torvalds@kernel.org gregkh@kernel.org
gpg --verify linux-6.6.30.tar.sign

From your distribution

Distributions also ship kernel source packages. On Debian/Ubuntu:

sudo apt install linux-source
tar xf /usr/src/linux-source-*.tar.bz2

This gives you the distribution kernel source with all their patches applied.

Kernel configuration

The kernel has thousands of options. The configuration is stored in a file called .config in the source directory.

Starting from your current config

The best starting point is your running kernel's config:

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

olddefconfig sets any new options that did not exist in the old config to their defaults. This gives you a config close to what you are already running.

make menuconfig

The interactive configuration tool:

make menuconfig

This opens a terminal UI where you browse the full option tree. Navigate with arrow keys, Enter to expand sections, space to toggle options.

Each option can be:

  • [*] — built into the kernel (always available)
  • [M] — built as a loadable module
  • [ ] — not compiled at all

Important sections:

  • General setup — kernel compression, init system, cgroup support
  • Processor type and features — CPU family, number of CPUs, preemption model
  • Device Drivers — everything hardware related
  • Networking support — protocols, netfilter, wireless
  • File systems — which filesystems to support
  • Security options — SELinux, AppArmor, seccomp

make nconfig and make xconfig

Alternatives to menuconfig:

  • make nconfig — similar but with a slightly different interface
  • make xconfig — graphical Qt-based configurator (requires Qt dev packages)

Reducing the config for a VM or specific hardware

For a minimal kernel, start from scratch:

make localmodconfig

This reads the currently loaded modules (lsmod) and builds a config that only includes what is currently in use. Results in a much smaller kernel but only safe if all your hardware is active at the time you run it.

Building the kernel

Once configured, build it:

make -j$(nproc)

-j$(nproc) uses all available CPU cores. This is the step that takes time. On a modern machine with 8 cores, expect 20–45 minutes.

Build output:

  • arch/x86/boot/bzImage — the compressed kernel image
  • vmlinux — the uncompressed kernel (for debugging)
  • System.map — symbol table

Building and installing modules

sudo make modules_install

This copies compiled modules to /lib/modules/$(kernel-version)/.

Installing the kernel

sudo make install

On most systems this copies the kernel image and System.map to /boot/ and updates the bootloader automatically via update-grub or equivalent.

Manually, the files that need to go to /boot/:

sudo cp arch/x86/boot/bzImage /boot/vmlinuz-6.6.30-custom
sudo cp System.map /boot/System.map-6.6.30-custom
sudo cp .config /boot/config-6.6.30-custom

Generating the initramfs

The initramfs is a minimal filesystem loaded into memory at boot before the real root filesystem is mounted. It contains the modules needed to mount root.

sudo update-initramfs -c -k 6.6.30-custom

Or with dracut (RHEL/Fedora):

sudo dracut --force /boot/initramfs-6.6.30-custom.img 6.6.30-custom

Updating the bootloader

GRUB

sudo update-grub

This scans /boot/ for kernels and regenerates /boot/grub/grub.cfg. Your new kernel will appear in the GRUB menu on the next boot.

To set the new kernel as default, edit /etc/default/grub:

GRUB_DEFAULT=0

0 boots the first entry (most recent kernel). Then apply:

sudo update-grub

Booting with GRUB menu visible

If GRUB normally skips the menu, hold Shift during boot to show it. Or set in /etc/default/grub:

GRUB_TIMEOUT=5
GRUB_TIMEOUT_STYLE=menu

Verifying the new kernel

After rebooting into your custom kernel:

uname -r

You should see your custom version string. If you set CONFIG_LOCALVERSION in menuconfig, that suffix appears here.

cat /proc/version

If the kernel does not boot

Always keep the previous working kernel available in GRUB. If the new kernel does not boot, select the old one from the GRUB menu and boot back in.

Common issues:

  • Missing modules for the root filesystem — kernel cannot mount root
  • Missing initramfs — always generate one after installing
  • Wrong root device in bootloader config
  • Unsupported CPU features enabled in config

Check kernel boot messages with:

journalctl -b -1    # logs from the previous boot
dmesg | head -100

Cleaning up

After a successful build, remove intermediate build files to free space:

make clean        # remove compiled objects but keep config
make mrproper     # remove everything including config

To remove an old kernel:

sudo rm /boot/vmlinuz-6.6.30-custom
sudo rm /boot/initrd.img-6.6.30-custom
sudo rm /boot/System.map-6.6.30-custom
sudo rm /boot/config-6.6.30-custom
sudo rm -rf /lib/modules/6.6.30-custom
sudo update-grub

Compiling your own kernel is not something you do every day. But doing it once teaches you more about how Linux actually works than almost anything else. The configuration tool alone — tens of thousands of options, each documented — is a complete education in what the kernel does.

The series has now covered Linux from the filesystem all the way down to building the kernel itself. That is the full stack.

0 views