1. Some context

The last time I compiled a custom Linux kernel was somewhere around 2006. I was running Gentoo. Back then, you enabled ReiserFS because that is what you ran, disabled every other filesystem you would never mount, and added smbfs on the rare occasion a Windows share appeared on the network. You knew exactly what ran on your machine, you knew why, and the ceremony of make menuconfig was how you kept it that way.

This was not a Gentoo eccentricity. Back in the early Linux era, compiling a custom kernel was often the difference between a web server that worked and one that did not. Running httpd on a 386 with 16MB of RAM left no room for SCSI drivers you did not have, sound card support you would never use, or filesystems you would never mount. Every kilobyte recovered from the kernel was a kilobyte available to the server. The machines serving the early web were not powerful. They were carefully configured. Well into the 2000s this was still common practice, a rite of passage for anyone serious about running Linux in production.

Then two things happened simultaneously. Distributions started shipping generic kernels that covered everything, removing most of the practical motivation for custom builds. And the kernel itself became progressively harder to reason about, not because of any single decision, but because of accumulated complexity. At some point, compiling your own kernel stopped feeling like hygiene and started feeling like archaeology. I kept asking the question in technical interviews for Linux engineers, "when did you last compile a custom kernel?", until I stopped. Not because the question was wrong, but because nobody does it anymore, and asking it only made me look like a dinosaur while revealing nothing useful about the candidate. The world is not going well.

Fast forward to 2026. I have a Raspberry Pi 500+ on my desk, the family machine, running Linux 7.0 on a BCM2712 SoC. And I find myself back in menuconfig. Let me explain why.

2. The numbers

Linux 3.0 shipped in July 2011 with roughly 15 million lines of code. Linux 7.0, released April 12, 2026, sits at over 40 million. That is not 15 years of progress. That is 15 years of accumulation.

For comparison: FreeBSD sits somewhere between 5 and 10 million lines depending on what you count. NetBSD around 7 million. OpenBSD, with its obsessive code auditing and "when in doubt, remove it" philosophy, around 3 million. The entire OpenBSD codebase fits in what Linux adds roughly every year.

Drivers account for approximately 60% of that total, network drivers, GPU drivers, multimedia drivers, platform-specific drivers. Most of this code exists because a company needed hardware support and sent patches upstream. The remaining 40% is features, subsystems, and infrastructure, much of it driven by the same companies, Intel, Google, Broadcom, Red Hat, and yes, Microsoft, all acting purely out of altruism and with no interest whatsoever in shaping the default behavior of the kernel that runs on a few billion devices. Surprise: they are not.

The consequences are concrete. Out of the box, a generic Raspberry Pi kernel ships with support for Microsoft Surface platform devices. It includes the Amateur Radio subsystem. It bundles multicast routing with PIM-SM v1 and v2. These are not bugs. They are the natural consequence of a codebase maintained by over 1,780 organizations, each with hardware to support and features to land.

From a security standpoint, every line of code you do not run is a line of code that cannot be exploited. The attack surface of a generic kernel is enormous. Most of it, on any given machine, will never be touched. This applies equally on x86: a server kernel carries drivers for hardware it will never see, and a desktop kernel ships with Amateur Radio support, Hyper-V guest drivers, and FireWire for camcorders that went to the landfill in 2009. The argument for a custom kernel is not nostalgia. It is the same argument it always was. On a Raspberry Pi 500+, it comes with one additional complication.

3. The BCM2712 is not your average ARM chip

Compiling a Linux kernel for the Raspberry Pi 500+ is not as simple as pulling from kernel.org. The BCM2712 SoC relies on a custom southbridge, the RP1. A southbridge is the chip that handles low-speed peripherals; GPIO, SPI, I2C, UART, and USB in this case, offloading that work from the main processor. None of this is in mainline Linux. Raspberry Pi maintains a fork at github.com/raspberrypi/linux with the necessary patches, organized by branch: rpi-6.12.y, rpi-6.18.y, rpi-7.0.y.

Without those patches, you might boot, but you lose USB, GPIO, and potentially NVMe. On a Pi 500+, that is effectively everything.

The rpi-7.0.y branch appeared shortly after the 7.0 stable release. It boots. There is a known shutdown delay on some configurations, but nothing that affects daily use.

Building natively on the Pi 500+ takes roughly an hour using all four Cortex-A76 cores. It is slower than cross-compiling from an x86 machine, but it removes the toolchain complexity and lets you document the process as it actually happens.

4. localmodconfig

The starting point is not make menuconfig. The starting point is make localmodconfig.

localmodconfig reads a snapshot of your currently loaded modules and generates a .config that includes only what is actually running. Everything else defaults to off. On my Pi 500+, that snapshot contained 67 modules.

lsmod > /tmp/lsmod-pi500.txt
make LSMOD=/tmp/lsmod-pi500.txt localmodconfig

One warning appeared during this step:

brcmfmac_cyw config not found!

The Broadcom WiFi driver for the CYW43455 chip, the Pi 500+'s built-in WiFi, was running but not matched by name. A quick check confirmed both brcmfmac and brcmfmac_cyw were loaded. The fix was manual:

scripts/config --enable CONFIG_BRCMFMAC
scripts/config --enable CONFIG_BRCMFMAC_CYW
make olddefconfig

localmodconfig is a strong starting point, not a complete solution. Verify anything wireless-related before compiling.

A disclaimer before we proceed. This is my first custom kernel on this architecture. The choices documented here were made with common sense and direct observation of what the machine actually does, not from exhaustive research or benchmarking. Some options may have been left enabled that a more thorough audit would remove. Some may have been removed that a more careful reading of the source would have kept. Take this as a starting point, not a reference

5. menuconfig: what we kept and why

With localmodconfig as a base, menuconfig becomes a review pass rather than a configuration exercise. Most of the work is already done. What remains is catching what localmodconfig missed and removing what it conservatively kept.

General setup: CONFIG_IKCONFIG was set to module by default. Moving it to built-in and enabling /proc/config.gz means the running kernel always carries its own configuration. This sounds trivial until you need to reproduce a build six months later, or debug why localmodconfig missed a module. Checkpoint/restore support exists to snapshot and restore running processes, useful in HPC environments, irrelevant here. Profiling support is for performance analysis tooling; legitimate on a development machine, unnecessary on a family desktop. Initramfs compression was reduced to gzip and zstd, the two formats Raspberry Pi OS actually uses. There is no reason to carry bzip2, LZMA, LZO, and LZ4 decompressors in every boot.

Networking: the bulk of the removals happened here. Amateur Radio subsystem, IP multicast routing with PIM-SM, IP advanced router, RARP support, MPTCP, MPLS. None of these have a role on a desktop machine connecting to a home network over WiFi. IPv6 stayed as a module. Bluetooth was set to module; the machine does not currently use it, but the CYW43455 supports it and the household may eventually want it. NFS client was reduced from built-in with v2/v3/v4/Kerberos/swap/root-on-NFS to a module supporting v3 and v4. NFS v2, removed from mainline in kernel 5.6 but present in the Raspberry Pi fork, was removed along with it. TUN/TAP was kept as a module for occasional OpenVPN use.

Filesystems: the NVMe is ext4, the boot partition is vfat. Those are the only two filesystems this machine will ever mount. F2FS, designed for NAND flash, makes no sense on a NVMe with its own wear leveling. NFS server support was removed; this machine is a client, not a server. Quota support was removed; there is one user, and that user is not going to run out of disk space in a way that requires kernel-level enforcement.

Device Drivers: Remote Controller support handles infrared remotes, the kind that came with TV cards in 2007. IEEE 1394 FireWire was the high-speed interface of choice before USB 3.0 existed. Microsoft Surface Platform drivers are self-explanatory. Hyper-V guest support is for machines running inside a Microsoft hypervisor. Virtio and VHOST are paravirtualization drivers for virtual machines. None of these have any business on a bare metal ARM desktop. Multimedia support (V4L2) was also removed; no camera is attached, and the framework carries a significant amount of code for capture devices, tuners, and encoders that will never be used.

6. Security, but not as masturbating monkeys

Linus Torvalds described the OpenBSD security approach in 2006 in terms that are not repeatable in most professional contexts. Deep down, we all know de Raadt (OpenBSD leader)is right. But this machine runs Minecraft.

A kernel that panics when it detects data corruption is a reasonable choice on a monitored critical server. On a desktop where you play Minecraft with your four year old, it is a support call waiting to happen. The goal here is passive hardening: protections that operate silently, with no operational cost.

What we enabled: heap zeroing on allocation and on free, so residual data does not linger between allocations. Stack zeroing at function entry was already at maximum. Register zeroing on function exit. Bounds checking on str/mem functions. Hardened copy_to_user()/copy_from_user() paths. Yama LSM to restrict ptrace between unrelated processes. Landlock LSM for application sandboxing without root. mseal for system memory mappings, available since 6.10.

What we left alone: AppArmor was already active, Raspberry Pi OS uses it. Kernel lockdown was left disabled; it can interfere with firmware loading on Pi hardware. BUG on data corruption was left disabled for the Minecraft reasons above.

RANDSTRUCT, which randomizes sensitive kernel structure layouts at compile time, requires gcc-plugin-dev at build time. Zero runtime cost, worth noting for anyone reproducing this on a different environment.

7. Results

Three kernel images now coexist on the NVMe:

kernel_2712.img            10.4 MB   Linux 6.18  (original)
kernel_2712-70.img         10.4 MB   Linux 7.0   (unmodified)
kernel_2712-70-custom.img   8.9 MB   Linux 7.0   (this build)

Module count dropped from 67 to 59.

The custom image is 1.5 MB smaller than the generic 7.0. WiFi is up. Bluetooth is available as a module. The machine boots normally and the family has not noticed anything changed, which is the correct outcome.

The config.txt on the boot partition selects kernels by model:

[pi500]
kernel=kernel_2712-70-custom.img

Removing that line at any time reverts to 6.18. The fallback cost is one line and a reboot.

One last note. In a world where AI agents find critical vulnerabilities every week, sometimes in code that has been sitting in the kernel for years, running only what you need is not paranoia. It is basic hygiene. The custom kernel on this machine handles exactly what this machine does. Everything else is not a disabled feature. It is a removed attack surface.

Built on a Raspberry Pi 500+ running Raspberry Pi OS Trixie. Sources from github.com/raspberrypi/linux, branch rpi-7.0.y. Compiled natively with GCC.