funwithlinux guide

How to Build Your Own Custom Linux Kernel

The Linux kernel is the core of every Linux-based operating system, responsible for managing hardware resources, running processes, and enabling communication between software and hardware. While most users rely on precompiled kernels provided by their Linux distribution (e.g., Ubuntu, Fedora), building a custom kernel offers several benefits: - **Performance Optimization**: Remove unnecessary features to reduce bloat, improve boot times, and enhance resource efficiency. - **Hardware Support**: Add drivers for rare or cutting-edge hardware not included in stock kernels. - **Security**: Minimize the attack surface by disabling unused components (e.g., legacy protocols, unneeded filesystems). - **Learning Experience**: Gain deep insights into how Linux works under the hood. This guide will walk you through the entire process of building a custom Linux kernel, from setting up prerequisites to testing and troubleshooting your new kernel.

Table of Contents

  1. Prerequisites
  2. Getting the Kernel Source Code
  3. Choosing a Kernel Version
  4. Configuring the Kernel
    • 4.1 Using make menuconfig
    • 4.2 Reusing an Existing Configuration
    • 4.3 Key Configuration Tips
  5. Building the Kernel
  6. Installing Modules and the Kernel
  7. Updating the Bootloader
  8. Testing the New Kernel
  9. Troubleshooting Common Issues
  10. Cleaning Up
  11. Conclusion
  12. References

Prerequisites

Before starting, ensure your system has the tools and dependencies required to build the kernel. Most modern Linux distributions will need the following packages:

For Debian/Ubuntu-based systems:

sudo apt update && sudo apt install -y \  
  build-essential libncurses-dev bison flex libssl-dev libelf-dev \  
  git dwarves zstd # dwarves for BTF debugging; zstd for compression  

For Fedora/RHEL-based systems:

sudo dnf install -y \  
  gcc gcc-c++ make ncurses-devel bison flex openssl-devel elfutils-libelf-devel \  
  git dwarves zstd  

For Arch Linux:

sudo pacman -Syu --needed \  
  base-devel ncurses bison flex openssl elfutils git dwarves zstd  

Note: Ensure you have at least 20GB of free disk space and 4GB+ of RAM (8GB+ recommended for faster builds).

Getting the Kernel Source Code

The official Linux kernel source is hosted on kernel.org. You can either download a tarball or clone the source code using git (recommended for easier updates).

The Linux kernel Git repository is large (~2GB), but cloning it lets you easily switch between versions:

git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git  
cd linux  

Option 2: Download a Tarball

If you prefer not to use git, download a stable kernel tarball from kernel.org. For example, to download version 6.6.3 (LTS):

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

Choosing a Kernel Version

Kernel.org categorizes releases into several types. For most users, Long-Term Support (LTS) kernels are the best choice due to their stability and extended maintenance (5+ years for recent LTS versions).

  • Stable: Updated frequently with bug fixes but not maintained long-term.
  • LTS: Stable, with security updates for years (e.g., 6.6.x, 6.1.x).
  • Mainline: Cutting-edge, for testing new features (not recommended for production).

Check the kernel release schedule to pick the right version. For this guide, we’ll use an LTS kernel (e.g., 6.6.3).

Configuring the Kernel

The kernel configuration step determines which features, drivers, and subsystems are included in your custom kernel. This is critical: including unnecessary components increases bloat, while excluding essential ones can break your system.

Step 1: Start with a Baseline Configuration

Instead of configuring from scratch, reuse your distribution’s existing kernel config as a starting point. This ensures compatibility with your hardware and software.

Method 1: Use /boot/config-* (Most Distributions)

Your running kernel’s configuration is stored in /boot. Copy it to the kernel source directory:

zcat /proc/config.gz > .config  # Alternative: copies the *running* kernel's config  
# OR (if /proc/config.gz is missing):  
cp /boot/config-$(uname -r) .config  

Method 2: Use make localmodconfig (Minimalist)

For a stripped-down config, localmodconfig generates a config based only on currently loaded modules:

make localmodconfig  # Answers "yes" to all prompts with Enter  

Step 2: Refine the Configuration with make menuconfig

Use make menuconfig to tweak the baseline config. This launches a text-based interface to enable/disable features:

make menuconfig  
  • Use arrow keys to move, Enter to select, and Esc to go back.
  • Press ? for help on a selected option.
  • Y = Build feature directly into the kernel (not as a module).
  • M = Build as a loadable module (recommended for most drivers to save space).
  • N = Disable the feature.

Key Options to Review:

  • Processor Type and Features: Match your CPU architecture (e.g., x86_64, ARM64). Enable Symmetric Multi-Processing for multi-core CPUs.
  • Device Drivers: Include drivers for your storage (e.g., NVMe, SATA), GPU, and network hardware.
  • File Systems: Enable support for your root filesystem (e.g., ext4, btrfs, XFS).
  • Security Options: Enable Control Group Support (for systemd) and Secure Boot if needed.
  • Kernel Hacking: Disable Kernel Debugging unless you’re debugging (increases size).

When done, save the config (default: .config) and exit.

Step 3: Update the Config for New Kernel Versions

If you’re upgrading from an older kernel version, run make olddefconfig to automatically resolve new options (uses default values for new features):

make olddefconfig  

Building the Kernel

With the config finalized, build the kernel. This step compiles the kernel image, modules, and associated files.

Optimize Build Speed

Use the -j flag to parallelize the build (replace N with the number of CPU cores + 1 for faster builds). For an 8-core CPU:

make -j9  # 8 cores + 1 = 9 jobs  

What Happens During the Build?

  • vmlinux: The raw kernel binary (uncompressed).
  • arch/x86/boot/bzImage: The compressed kernel image (loaded by the bootloader).
  • Modules: Stored in modules/ (later installed to /lib/modules/).

Installing Modules and the Kernel

After building, install the kernel modules and the kernel itself.

Step 1: Install Modules

Modules are loadable components (e.g., drivers) that extend kernel functionality. Install them to /lib/modules/:

sudo make modules_install  

This creates a directory like /lib/modules/6.6.3-custom/ (replace 6.6.3-custom with your kernel version).

Step 2: Install the Kernel

Install the kernel image, System.map (symbol table), and config file to /boot:

sudo make install  

This also generates an initramfs (initial RAM filesystem) to load critical drivers before the root filesystem mounts.

Updating the Bootloader

Your bootloader (e.g., GRUB, systemd-boot) needs to detect the new kernel to list it in the boot menu.

For GRUB (Most Distributions: Ubuntu, Fedora, Debian)

Update GRUB’s configuration to include the new kernel:

sudo update-grub  # Debian/Ubuntu  
# OR (Fedora/RHEL):  
sudo grub2-mkconfig -o /boot/grub2/grub.cfg  

For systemd-boot (Arch Linux, Fedora with UEFI)

If using systemd-boot (common on UEFI systems), copy the kernel and initramfs to /boot/EFI/Linux/ and create a loader entry:

# Replace "6.6.3-custom" with your kernel version  
sudo cp /boot/vmlinuz-6.6.3-custom /boot/EFI/Linux/  
sudo cp /boot/initramfs-6.6.3-custom.img /boot/EFI/Linux/  

# Create a loader entry (e.g., /boot/loader/entries/custom.conf)  
sudo nano /boot/loader/entries/custom.conf  

Add this content to custom.conf (adjust paths for your system):

title Custom Linux 6.6.3  
linux /vmlinuz-6.6.3-custom  
initrd /initramfs-6.6.3-custom.img  
options root=UUID=your_root_partition_uuid rw quiet  

Find your root partition’s UUID with blkid.

Testing the New Kernel

Reboot your system and select the custom kernel from the boot menu (e.g., “Custom Linux 6.6.3” in GRUB).

Verify the Kernel Version

After booting, confirm the new kernel is running:

uname -r  # Should output "6.6.3-custom" (or your version)  

Check for Issues

  • Run dmesg | grep -i error to look for hardware/driver errors.
  • Test critical features: network, sound, graphics, and storage.

Troubleshooting Common Issues

1. System Fails to Boot

  • Fix: Reboot and select your old kernel from the GRUB menu. Check logs with journalctl -k -b -1 (boot log from last failed attempt).

2. Missing Drivers/Hardware Not Working

  • Fix: Reboot to the old kernel, re-run make menuconfig, and enable the missing driver (e.g., Wi-Fi, GPU). Rebuild and reinstall.

3. Initramfs Errors

  • Fix: Regenerate the initramfs manually:
    sudo update-initramfs -c -k 6.6.3-custom  # Replace with your kernel version  

4. “No Space Left on Device”

  • Fix: Clean up old kernels in /boot (use sudo apt autoremove on Debian/Ubuntu) or free space elsewhere.

Cleaning Up

Building the kernel generates large temporary files. Clean them up to save space:

make clean  # Removes object files (keeps .config)  
# OR (to reset completely, including .config):  
make mrproper  

Delete the kernel source directory if you don’t need it anymore:

cd .. && rm -rf linux-6.6.3  # Replace with your source directory  

Conclusion

Building a custom Linux kernel is a rewarding project that lets you tailor your system to your needs. While it requires time and patience, the benefits—faster boot times, better hardware support, and a deeper understanding of Linux—are well worth it. Start with small tweaks, test thoroughly, and refer to the references below for advanced configurations.

References