Kernel modules are pieces of kernel code, usually drivers, filesystems and network features, that the kernel loads on demand instead of carrying them all the time. Most are loaded automatically when hardware is detected or a feature is first used, but servers regularly need manual control: loading br_netfilter for Kubernetes, tuning a driver parameter, blocking USB storage for hardening or installing a vendor driver that must survive kernel updates. In this tutorial you will inspect, load and unload modules, set parameters, make both persistent, blacklist a module and build your own module with DKMS on Ubuntu 24.04.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • For Step 6, about 300 MB of free disk space for the compiler and kernel headers.

Step 1 - Inspecting loaded and available modules

lsmod lists the modules currently loaded, their size and which other modules use them:

lsmod | head -n 6
Module                  Size  Used by
tls                   155648  0
nf_conntrack_netlink   61440  0
xt_conntrack           12288  3
nf_conntrack          196608  4 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE
nf_defrag_ipv6         24576  1 nf_conntrack

The Used by count tells you whether a module can be removed: a module with a non-zero count is in use by other modules or by the kernel.

Modules for the running kernel live under /lib/modules/$(uname -r)/. On Ubuntu 24.04 they are compressed with zstd, so the files end in .ko.zst. modinfo shows the metadata of any available module, loaded or not:

modinfo br_netfilter
filename:       /lib/modules/6.8.0-83-generic/kernel/net/bridge/br_netfilter.ko.zst
description:    Linux ethernet netfilter firewall bridge
license:        GPL
...
depends:        bridge
intree:         Y
name:           br_netfilter
vermagic:       6.8.0-83-generic SMP preempt mod_unload modversions
...

The important fields are filename, depends (modules that must be loaded first), vermagic (the kernel the module was built for) and any parm lines, which list the parameters the module accepts. To print only the parameters, use modinfo -p.

To see what modprobe would load for a module, including its dependencies, without loading anything:

modprobe --show-depends br_netfilter
insmod /lib/modules/6.8.0-83-generic/kernel/net/802/stp.ko.zst
insmod /lib/modules/6.8.0-83-generic/kernel/net/llc/llc.ko.zst
insmod /lib/modules/6.8.0-83-generic/kernel/net/bridge/bridge.ko.zst
insmod /lib/modules/6.8.0-83-generic/kernel/net/bridge/br_netfilter.ko.zst

Step 2 - Loading and unloading a module

Always use modprobe rather than the low-level insmod and rmmod: modprobe resolves dependencies, finds the file by name and applies the configuration in /etc/modprobe.d/.

Load br_netfilter, which lets iptables and nftables see bridged traffic and is a common requirement for Kubernetes nodes:

sudo modprobe br_netfilter

modprobe prints nothing on success. Confirm that the module and its dependencies are loaded:

lsmod | grep -E '^(br_netfilter|bridge)'
br_netfilter           32768  0
bridge                421888  1 br_netfilter

Loading the module also created its kernel parameters, which is an easy way to verify it works:

sysctl net.bridge.bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1

Unload it with modprobe -r, which also removes dependencies that nothing else uses:

sudo modprobe -r br_netfilter

If a module is in use, the command fails with FATAL: Module ... is in use and nothing is removed. Find what uses it in the Used by column of lsmod, and stop that service or unload that module first. Do not force removal with rmmod -f: unloading a driver that is still in use can crash the kernel.

Step 3 - Loading modules automatically at boot

Modules loaded by hand are gone after a reboot. To load a module at every boot, list it in a file under /etc/modules-load.d/, which systemd-modules-load.service reads early in the boot process. Create a file for your Kubernetes prerequisites:

sudo nano /etc/modules-load.d/k8s.conf

Add one module name per line:

overlay
br_netfilter

Apply the file now without rebooting by restarting the service that reads it:

sudo systemctl restart systemd-modules-load.service

Check that both modules are loaded:

lsmod | grep -E '^(overlay|br_netfilter)'
br_netfilter           32768  0
overlay               212992  0

If a name is misspelled, the service logs the error. Check it with journalctl -b -u systemd-modules-load.service.

Step 4 - Setting module parameters

Many modules accept parameters that change their behaviour. As an example, nf_conntrack (the connection tracking used by firewalls and NAT) has a hashsize parameter that sets the size of its hash table. On busy firewalls or load balancers, a larger table keeps lookups fast.

List the parameters of the module:

modinfo -p nf_conntrack

The current value of each parameter of a loaded module is exposed under /sys/module/<name>/parameters/. Load the module if it is not already loaded, then read the value:

sudo modprobe nf_conntrack
sudo cat /sys/module/nf_conntrack/parameters/hashsize
65536

Some parameters, including this one, can be changed at runtime by writing to the same file:

echo 262144 | sudo tee /sys/module/nf_conntrack/parameters/hashsize

Parameters that are read-only in /sys can only be set when the module is loaded, for example sudo modprobe module_name param=value.

To apply a parameter every time the module loads, add an options line to a file in /etc/modprobe.d/:

sudo nano /etc/modprobe.d/nf_conntrack.conf
options nf_conntrack hashsize=262144

If the module might be loaded from the initramfs, which is usual for storage and some network drivers, rebuild it so the early boot environment sees the same options:

sudo update-initramfs -u

After the next reboot, confirm the value:

sudo cat /sys/module/nf_conntrack/parameters/hashsize
262144

Step 5 - Blacklisting a module

Sometimes you want to keep a module from loading, for example to use a vendor driver instead of the in-kernel one, or to disable USB mass storage on a hardened server. The blacklist keyword only stops a module from being loaded automatically through hardware aliases; an explicit modprobe or another module that depends on it still loads it. To block it completely, also add an install line that runs /bin/false instead of loading the module.

Create a file for the rule:

sudo nano /etc/modprobe.d/disable-usb-storage.conf
blacklist usb-storage
install usb-storage /bin/false

Rebuild the initramfs so the rule also applies during early boot:

sudo update-initramfs -u

Check what modprobe would do now. -n makes it a dry run and -v prints the action:

modprobe -n -v usb-storage
install /bin/false

The output shows that loading the module would run /bin/false instead. If the module was loaded before you added the rule, unload it with sudo modprobe -r usb-storage or reboot, then confirm that lsmod | grep usb_storage returns nothing. Module names treat - and _ as equivalent, which is why lsmod shows usb_storage.

Step 6 - Building an out-of-tree module with DKMS

Modules that are not part of the Ubuntu kernel, such as some vendor NIC, RAID or GPU drivers, must be recompiled for every new kernel. DKMS (Dynamic Kernel Module Support) automates this: it keeps the module source under /usr/src and rebuilds and installs the module whenever a new kernel is installed. Vendor packages whose names end in -dkms use it for exactly this. In this step you build a small module yourself and hand it to DKMS so you can see the whole process.

Install DKMS, the compiler and the headers for the running kernel:

sudo apt update
sudo apt install dkms build-essential linux-headers-$(uname -r)

To make sure headers for future kernels are installed along with them, also install the headers metapackage that matches your kernel flavour, for example linux-headers-generic or linux-headers-virtual.

Create the source directory that DKMS expects, /usr/src/<name>-<version>:

sudo mkdir -p /usr/src/hello-1.0

Create the module source:

sudo nano /usr/src/hello-1.0/hello.c
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal example module");
MODULE_VERSION("1.0");

static int __init hello_init(void)
{
	pr_info("hello: module loaded\n");
	return 0;
}

static void __exit hello_exit(void)
{
	pr_info("hello: module unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);

Create the Makefile. The kernel build system only needs to know which object to build as a module:

sudo nano /usr/src/hello-1.0/Makefile
obj-m += hello.o

Create the DKMS configuration, which tells DKMS the package name, version and the module it produces:

sudo nano /usr/src/hello-1.0/dkms.conf
PACKAGE_NAME="hello"
PACKAGE_VERSION="1.0"
BUILT_MODULE_NAME[0]="hello"
DEST_MODULE_LOCATION[0]="/updates/dkms"
AUTOINSTALL="yes"

When MAKE is not set, DKMS builds with the kernel's own build system (make -C <kernel headers> M=<build dir>), which is all this module needs. AUTOINSTALL="yes" makes DKMS rebuild the module automatically for every new kernel.

Register, build and install the module:

sudo dkms add -m hello -v 1.0
sudo dkms build -m hello -v 1.0
sudo dkms install -m hello -v 1.0

Check its status:

dkms status
hello/1.0, 6.8.0-83-generic, x86_64: installed

DKMS installed the module under /lib/modules/$(uname -r)/updates/dkms/ and updated the module index, so modprobe finds it by name like any other module:

sudo modprobe hello
lsmod | grep '^hello'
hello                  12288  0

Read the kernel log to see the message from hello_init():

sudo dmesg | tail -n 3
[ 8123.402917] hello: loading out-of-tree module taints kernel.
[ 8123.403144] hello: module verification failed: signature and/or required key missing - tainting kernel
[ 8123.403512] hello: module loaded

The two "taints kernel" lines are expected for a module that is not part of the distribution kernel and is not signed; they are recorded so that kernel crash reports show a third-party module was loaded. On servers with UEFI Secure Boot enabled, see the troubleshooting section below.

When you are done, unload the module and remove it from DKMS for all kernels:

sudo modprobe -r hello
sudo dkms remove hello/1.0 --all
sudo rm -r /usr/src/hello-1.0

Troubleshooting

modprobe: FATAL: Module xyz not found in directory /lib/modules/.... The module does not exist for the running kernel. Check the name with find /lib/modules/$(uname -r) -name 'xyz*'. On Ubuntu, less common drivers are in the linux-modules-extra-$(uname -r) package, which minimal and virtual installations do not include. For a module you copied in by hand, run sudo depmod -a to rebuild the index.

A DKMS module is missing after a kernel upgrade. The headers for the new kernel were not installed, so DKMS could not build it. Install them with sudo apt install linux-headers-$(uname -r) after booting the new kernel, then run sudo dkms autoinstall and check dkms status. Installing the headers metapackage for your kernel flavour prevents this in the future.

Key was rejected by service when loading a module. UEFI Secure Boot is enabled and the module is not signed with a trusted key. Check with mokutil --sb-state. On Ubuntu, DKMS signs the modules it builds with a local Machine Owner Key (MOK); enroll that key with sudo mokutil --import /var/lib/shim-signed/mok/MOK.der, set a one-time password, and confirm the enrollment on the console during the next reboot.

A blacklisted module still loads. A blacklist line does not stop explicit loads or dependencies. Add an install module_name /bin/false line as shown in Step 5, find which other module pulls it in with the Used by column of lsmod, and rebuild the initramfs with sudo update-initramfs -u.

Conclusion

You inspected modules with lsmod and modinfo, loaded and unloaded them with modprobe, made modules and their parameters persistent with /etc/modules-load.d/ and /etc/modprobe.d/, blocked a module completely, and built a module that DKMS rebuilds for every new kernel. As next steps, review which modules your servers load with lsmod, apply the Kubernetes prerequisites from Step 3 on your nodes, and use DKMS packages from your hardware vendor instead of drivers compiled by hand.