
Auto-Replace OpenWrt Kernel Config Without Losing Changes
Run make kernel_menuconfig from the top of your build tree and let the build system write the change for you. The OpenWrt kernel config is not a single file you hand-edit; it is generated per target from generic defaults, target and subtarget fragments, and whatever your package selection pulls in. So make the change interactively, read the generated .config under build_dir to confirm it survived dependency resolution, then persist only the symbols your firmware actually needs into the target config file. Everything past that is guessing, and guessing is what turns a ten-minute change into a lost evening.
Last updated: 2026-07-31
What does the OpenWrt kernel config control?
It decides what gets compiled into the kernel image, what ships as a loadable module inside a kmod-* package, and what never exists in your firmware at all. On a router with a small flash partition, that last category matters more than it does on a laptop. A driver you never use still occupies flash, and on a tight partition that squeezes out packages you do want.
OpenWrt treats the kernel as a build input. You do not manage an independent kernel tree here. You pick a target, a subtarget, and a device profile, and those choices select which config fragments get merged and which patches get applied. Change the target and the effective kernel config changes with it, even though nothing in your working copy moved.
This is where most confusion starts. People read a Stack Overflow answer written for one target, paste the symbol into their tree, and wonder why the build ignored it. The symbol was fine. The context was wrong.
How OpenWrt builds the Linux kernel
OpenWrt downloads an upstream kernel tarball, verifies its hash, applies generic patches, then target patches, then merges the config fragments and builds. None of that is a vanilla kernel build, which is why a config from another distro is close to useless here. The OpenWrt build system documentation is the primary reference for how the stages fit together.
The kernel version is pinned per target. KERNEL_PATCHVER in target/linux/<platform>/Makefile selects the source line. A matching include/kernel-<version> file carries the tarball name and its hash, generated with staging_dir/host/bin/mkhash sha256. Targets do not all sit on the same kernel line, so check your tree rather than trusting a forum post.
That per-target pinning is what makes builds repeatable. Commit it, and every rebuild follows the same source line instead of drifting when someone syncs the feeds.
Where the configuration files live
Keep these paths straight and half the mystery failures stop happening. Source files are what you commit. Build directories are generated output, and they vanish the moment you clean.
| Item | Location | Why it matters |
|---|---|---|
| Shared kernel patches | target/linux/generic | Reused across every platform |
| Generic config defaults | target/linux/generic/config-<version> | Baseline symbols for all targets |
| Platform config | target/linux/<target>/config-<version> | Target-specific options you commit |
| Subtarget config | target/linux/<target>/<subtarget>/config-<version> | Overrides for one board family |
| Target patches | target/linux/<target>/patches-<version> | Numeric prefixes control apply order |
| Unpacked kernel source | build_dir/target-*/linux-*/linux-* | Where you debug and where patches land |
Generated kernel .config | inside that same kernel build dir | The merged result, regenerated every build |
Buildroot .config | top of the tree | Target, profile, and package selection |
Keep a default baseline config per target. Commit both the top-level .config and the target configs to version control. The paths behave the same whether you are building for x86 or an old ar71xx board, so one habit covers both.
Which config applies to the target you selected
The effective kernel config is a merge, and the merge order is generic first, then target, then subtarget. Package selection layers on top. Many kmod-* packages declare KCONFIG symbols in package/kernel/linux/modules/*.mk, and selecting the package forces those symbols on. As a result, a symbol can be enabled in your firmware without appearing anywhere in the target config file.
That cuts both ways. Deselect the package and the symbol quietly goes away again, even though you never touched a config fragment. When a setting refuses to stick, check whether a package owns it before you go editing files.
CONFIG symbols, tristates, and dependencies
Every option is a CONFIG_ symbol with one of a few states: =y built in, =m built as a module, or the commented-out # CONFIG_FOO is not set. Boolean symbols only take y or unset. Tristate symbols take all three, and this is where people trip.
Setting a symbol to =m produces a .ko file in the build directory. It does not produce something opkg can install. For that, a kmod package has to exist that claims the module, otherwise the build drops it on the floor and nobody tells you. If you need the driver present at boot regardless, build it in.
Dependencies are the other half. depends on means the option stays invisible until its prerequisite is on. select means turning your option on drags others in behind it. Kconfig silently unsets anything whose dependencies are unmet, which is the usual reason a hand-edited line disappears. Our notes on enabling kernel CONFIG options go deeper on reading those chains.
Running make kernel_menuconfig
Set the target first, then edit the kernel. Doing it the other way round means you configure one target and build another.
make menuconfig # target, subtarget, device profile, packages
make kernel_menuconfig # kernel symbols for that target
make target/linux/clean
make -j$(nproc) target/linux/compile V=s
Inside kernel_menuconfig, press / to search for a symbol. The search result shows the symbol's prompt, its current value, its dependencies, and the file that defines it. Read the dependency lines before you toggle anything. That panel answers most "why is this greyed out" questions on the spot.
On exit, the menu writes the result into the target config file for the target you selected. The top-level .config stays untouched. Those two menus edit different things, and mixing them up is the classic first mistake. If you want the mechanics of the underlying menu system, see how menuconfig configures the Linux kernel.
Editing configuration files by hand without breaking them
Start from the failing feature. Never paste a whole config file from a forum thread into your tree. You will inherit symbols for hardware you do not have and lose an afternoon working out which one bloated your image past the flash partition.
Hand-editing a single line in target/linux/<target>/config-<version> is fine when you know the symbol. Trusting it afterwards is not. Re-run kernel_menuconfig, exit, and diff the file so Kconfig gets a chance to resolve dependencies and tell you what it actually kept.
For patch work, use the quilt workflow instead of editing files in build_dir and hoping they survive:
make target/linux/clean
make target/linux/prepare QUILT=1
make target/linux/refresh
Making custom settings survive a clean
Anything in build_dir is disposable. make target/linux/clean removes the unpacked source and the merged kernel .config with it, so a change you only made there is gone. Persist kernel symbols in the target or subtarget config file. Persist package and profile choices with ./scripts/diffconfig.sh > diffconfig, which you can feed back into a fresh tree.
Keep versioned config files and patches-<version> directories under target/linux so every change is traceable to a version transition. Short commit messages that name the file and the reason beat a clever branch name later. When you rebase onto a newer source line, that history tells you which symbols were deliberate.
One thing to plan for: a kernel version change alters the module ABI, so prebuilt kmods stop installing until you rebuild them. Rebuild the packages, refresh the feed, then flash.
Proving the option reached the device

Check the generated config first, then the artifact, then the running system. Each step rules out a different failure.
grep CONFIG_YOUR_SYMBOL build_dir/target-*/linux-*/linux-*/.config
find build_dir -name 'your_module.ko'
ls bin/targets/<target>/<subtarget>/packages/ | grep kmod-
On the router itself, zcat /proc/config.gz | grep CONFIG_YOUR_SYMBOL is the direct answer, but only if CONFIG_IKCONFIG_PROC is enabled in the image. If it is not, fall back to lsmod, modinfo, and logread -e plus dmesg for the driver's own boot messages. A feature-heavy example of this loop is our walkthrough on enabling eBPF on OpenWrt, where the symbol set has to be verified on the device before anything useful runs.
Why does a kernel change look like it did nothing?
Work through these in order before you touch the config again:
- Unmet dependency. Kconfig dropped your symbol on the way through. Search for it in
kernel_menuconfigand read thedepends online. - Wrong target or subtarget. You edited one board family's config and built another. Confirm at the top of
make menuconfig. - Stale build output. The kernel source in
build_dirwas already unpacked and configured. Runmake target/linux/cleanand rebuild. - Module with no package. The
.kobuilt, but no kmod package claims it, so it never reached the image. - Overwritten edit.
kernel_menuconfigrewrote the file after your hand edit. Diff and redo. - Flashed the wrong image. Sysupgrade and factory images are not interchangeable, and a device that failed to flash boots the old kernel happily.
Each check takes a moment. Running them in order beats kicking off another build on a hunch.
Debugging past the configuration menus
When a build dies, scroll up past the error message and read the log above it. V=s gives you the full command line, and the real failure usually sits well above the Error 2 that made you stop scrolling. Patch rejects say exactly which file and hunk went stale, so refresh the patch against the new source instead of deleting it.
For runtime failures, the config is often innocent. If a daemon fails after a kernel change, run strace -f -e trace=openat,ioctl on the process. That tells you which device node or interface it wanted and what error came back. Pair it with dmesg and logread, because a missing /dev entry and a missing symbol look identical from userspace.
When modules build but refuse to load, the message is usually a version signature or modules-tree mismatch. That failure has its own well-worn shape, covered in our writeup on the missing kernel modules tree error.
FAQ
Can I reuse a kernel config from Debian or a vanilla kernel build?
No, and I would not try. OpenWrt merges its own fragments, applies its own patches, and expects symbols tuned for small flash and limited RAM. A desktop config enables drivers for hardware your board will never have, and the resulting image usually will not fit.
Do I have to rebuild everything after changing one symbol?
Not usually. make target/linux/clean followed by make target/linux/compile rebuilds the kernel, and make package/kernel/linux/compile handles the kmod packages. Change the kernel version itself and that changes, because every module package needs rebuilding against the new ABI.
How do I find which kmod package provides a driver?
Grep the module definitions for the symbol: grep -rn CONFIG_YOUR_SYMBOL package/kernel/linux/modules/. The surrounding define KernelPackage/... block names the package you need to select in menuconfig. If nothing matches, there is no package and you should build the option in.
Is it worth bumping KERNEL_PATCHVER myself?
Only if you are prepared to fix patch rejects. Every patch under patches-<version> was written against a specific source tree, and a version bump will break some of them. Do it on a branch, keep the old config files alongside the new ones, and test a full build before you flash anything you care about.
