---
title: "Building a custom Ubuntu kernel"
date: "2022-07-19"
path: "/217-building-a-custom-ubuntu-kernel.html"
section: "/ramblings"
description: "Compiling a mainline Linux kernel on Ubuntu into installable .deb packages, including module signing and starting from your running config."
---

Most of the time the stock Ubuntu kernel is exactly what you want and you should leave it alone. Occasionally you need to change something the packaged kernel will not let you: patch an in-tree driver, flip a build-time option, or tweak the input subsystem. In my case it was the input drivers, trimming how `evdev`, `mousedev` and `joydev` behave so a touchscreen-only device did not expose a stray pointer. Whatever the reason, the clean way on Ubuntu is to build the kernel into `.deb` packages and install those, so it is tracked by the package manager like any other kernel.

This is on Ubuntu 22.04 building a 5.15 series kernel.

## Build dependencies

```bash
sudo apt install build-essential libncurses-dev flex bison openssl libssl-dev \
  dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf bc zstd dwarves rsync
```

`libelf-dev`, `bc`, `dwarves` and `zstd` are the ones people miss, and the build fails partway through without them.

## Get the source

Pull a mainline tarball from kernel.org and unpack it into `/usr/src`:

```bash
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.46.tar.xz
tar -xf linux-5.15.46.tar.xz
sudo mv linux-5.15.46 /usr/src/
cd /usr/src/linux-5.15.46
```

## Start from your running config

Do not configure from scratch. Start from the config of the kernel you are already running, so you keep all the driver and hardware choices that make your machine boot, then change only what you came to change:

```bash
cp /boot/config-$(uname -r) .config
```

Carry forward any new options non-interactively, accepting the defaults:

```bash
make olddefconfig
```

If you need to make your changes through the menu interface, do it now:

```bash
make menuconfig
```

Or, if you are patching driver source directly, edit the files under `drivers/` before you build.

## Module signing certificate

Modern kernel configs sign their modules, and the build will stop asking for a signing key if there is not one. Generate a self-signed certificate for it:

```bash
mkdir -p certs
openssl req -x509 -newkey rsa:4096 -keyout certs/mycert.pem -out certs/mycert.pem -nodes -days 3650
```

If you would rather not sign modules at all for a local build, turn `CONFIG_MODULE_SIG` off in the config instead; signing is the better habit.

## Build the packages

Build straight into Debian packages, using all your cores. `LOCALVERSION` tags the kernel so it is clearly yours and does not collide with the stock one in the boot menu:

```bash
make -j$(nproc) bindeb-pkg LOCALVERSION=-custom
```

This takes a while. When it finishes, the `.deb` files are written to the parent directory, `/usr/src` here.

## Install and boot

Install the image and headers packages and reboot:

```bash
cd /usr/src
sudo dpkg -i linux-image-*-custom_*.deb linux-headers-*-custom_*.deb
sudo reboot
```

Confirm you are running the kernel you built:

```bash
uname -r
```

You should see your `-custom` version. Because these went in as proper packages, the kernel shows up in the boot menu, is managed by `apt`, and removes cleanly with `dpkg -r` if the experiment does not work out, which is the whole reason to build it this way rather than running `make install` by hand.
