Multi-monitor development setup for kernel programming and eBPF tool development
eBPF Tooling
William  

Fix Apt Install Bpftool Virtual Package on Ubuntu Noble

On Ubuntu 24.04 Noble, apt install bpftool fails because bpftool is a virtual package, not a real one. The provider you actually want is linux-tools-common, but it only gives you a working binary if the linux-tools package matching your exact running kernel is installed alongside it. Get the ubuntu noble apt install bpftool virtual package linux-tools-common combination right, or bpftool ends up silently absent or pointing at the wrong kernel. Here is what happens and how to fix it for good.

Last updated: 2026-07-18

Why does 'apt install bpftool' point you to the linux-tools-common virtual package on Ubuntu Noble?

Because there is no package literally named bpftool you can install. Run apt install bpftool on Noble and apt tells you the package is virtual, then lists providers instead of installing anything. That message is not an error in your setup. It is apt telling you the name maps to something else and asking you to pick the real thing.

The real thing is a wrapper. Ubuntu ships bpftool inside its kernel tooling, and the launcher lives in linux-tools-common. The actual binary that does the work lives in a kernel-versioned package. So one name, two moving parts, and both have to be present.

Skip either half and you get the two classic failures. No linux-tools-common means bpftool is not on your PATH at all. Common installed but no kernel-matched tools package means bpftool runs, then complains it cannot find its versioned backend. Same symptom family, different cause, and that is the whole reason people burn an afternoon here.

What a virtual package actually is, and who provides bpftool

A virtual package is a name that several real packages can claim to satisfy, not a package you download. Debian and Ubuntu use it so tools can depend on a capability like "an MTA" or "bpftool" without hard-coding one provider. When you ask for the virtual name, apt refuses to guess and shows you the providers.

For bpftool on Noble, the provider that matters is linux-tools-common. It declares that it provides bpftool. That is why apt-cache search bpftool and apt install bpftool both keep steering you toward it.

But linux-tools-common is deliberately kernel-agnostic. It carries the shared launcher and shared bits, not the compiled tool for your specific kernel. The compiled tool comes from a second package, and that is where the version dance starts.

What does linux-tools-common actually install?

It installs the wrapper, not the working binary. linux-tools-common drops a launcher script at /usr/sbin/bpftool (and the same pattern for perf and friends). When you run bpftool, that script reads uname -r, then reaches for the real binary under a kernel-versioned path like /usr/lib/linux-tools/$(uname -r)/bpftool.

That indirection is clever and also the trap. The wrapper is happy to install on its own. It will sit on your PATH looking installed while the binary it points at does not exist yet.

So linux-tools-common alone is necessary but never sufficient. You have the map with no destination. Confirm what it dropped with dpkg -L linux-tools-common and you will see scripts and manpages, no per-kernel bpftool. The binary ships separately, and it ships per kernel.

How your running kernel and linux-tools have to match

The wrapper looks up uname -r at runtime, so the tools package must match that exact string. If you booted 6.8.0-45-generic, the wrapper wants /usr/lib/linux-tools/6.8.0-45-generic/bpftool, which comes from linux-tools-6.8.0-45-generic. Install linux-tools-6.8.0-31-generic instead and the wrapper still looks for -45, finds nothing, and fails.

This is the mismatch that silently breaks bpftool after a kernel update. You upgrade, reboot into a newer kernel, and the tools package for the old kernel is now the wrong version. Nothing warns you. The next time you run bpftool it just cannot find its backend.

The fix is to install the tools package pinned to the running kernel, not a generic guess:

sudo apt install linux-tools-$(uname -r)

The $(uname -r) matters. It resolves to your booted kernel string so apt pulls the matching binary, not whatever version happens to be newest in the archive. Do not hardcode a version you copied from a blog.

Which bpftool packages are available in Ubuntu Noble and noble-updates?

Expect three shapes of package, and search for them before you assume anything. Run apt-cache search linux-tools and you will see the pattern:

PackageWhat it isDo you need it
linux-tools-commonThe virtual-package provider and wrapper scriptsYes, always
linux-tools-$(uname -r)The bpftool/perf binary for one exact kernelYes, matched to your kernel
linux-tools-genericA meta-package that tracks the generic kernel lineHandy, see below

The kernel-matched linux-tools packages get published to noble-updates as new kernels land, which is exactly why the version you need may not be in the original noble release pocket. If apt-cache policy linux-tools-$(uname -r) shows a candidate only from noble-updates, that is normal. Make sure noble-updates is enabled or the matching package looks like it does not exist.

linux-tools-generic is the one people miss. It depends on whatever tools package matches the current generic kernel, so on a stock Noble box it pulls the right binary and keeps tracking it. It is not automatic, but it saves you from typing the version by hand.

How do you diagnose a missing or broken bpftool installation?

Run four commands in order and read what each one tells you. Do not skip to reinstalling everything.

  1. uname -r prints your running kernel string. Everything downstream must match this exact value.
  2. dpkg -l | grep linux-tools shows which tools packages are actually installed. Check that one line matches the string from step one.
  3. apt-cache policy linux-tools-$(uname -r) tells you whether the matching package exists in your enabled repos and where it comes from. If the candidate is (none), your noble-updates pocket is probably off.
  4. which bpftool confirms the wrapper is on PATH. If it prints a path but the tool errors, the wrapper is fine and the versioned binary is missing.

That sequence separates the two failure modes cleanly. Wrapper present, binary absent is a linux-tools-$(uname -r) problem. Nothing on PATH at all is a missing linux-tools-common. Each output points at one cause, so fix that one, not the whole stack.

What is the correct install command for bpftool on Noble?

Install both halves in one go, pinned to your kernel:

sudo apt update
sudo apt install linux-tools-common linux-tools-$(uname -r)

Then prove it works before you trust it:

bpftool version

A version string means the wrapper found its backend and both packages agree on the kernel. If you want the setup to survive future kernel upgrades without thought, add the generic meta-package too:

sudo apt install linux-tools-generic

That is the definitive path for the ubuntu 24.04 apt install bpftool virtual package linux-tools-common problem. Refresh metadata, install the wrapper plus the kernel-matched binary, verify with a version check. If bpftool version still fails after that, go back to the four diagnosis commands. The output will tell you which half is wrong.

When should you use a symlink workaround from an older kernel version?

Almost never, and only as a stopgap. The workaround is real: if linux-tools for your exact kernel is not in the archive yet, people symlink the wrapper's expected path to a binary from a nearby kernel version:

sudo ln -s /usr/lib/linux-tools/6.8.0-31-generic/bpftool \
 /usr/lib/linux-tools/6.8.0-45-generic/bpftool

That gets bpftool running today. The risk is a subtle ABI mismatch. bpftool talks to kernel structures that change between versions, so a binary built against one kernel can misread maps, print garbage, or fail on newer program types when pointed at another. Across two point releases in the same series you will usually get away with it. Across a bigger jump, do not trust the output for anything load-bearing.

So treat the symlink as a bridge, not a fix. Use it to unblock a demo, then install the real linux-tools-$(uname -r) from noble-updates the moment it lands and delete the link. A symlink that outlives the gap is how you end up debugging a bpftool bug that is really a version skew.

Setting up a reliable eBPF development environment on Noble

bpftool inspects and loads programs, but building your own needs more on the box. For a full workflow you want the compiler, the BPF library, and kernel headers next to bpftool:

sudo apt install clang llvm libbpf-dev linux-headers-generic

linux-headers-generic is the header package for Ubuntu 24.04, and it tracks the generic kernel the same way the tools meta-package does. clang and llvm compile your C into BPF bytecode. libbpf-dev gives you the loader library and headers that modern CO-RE programs link against.

If you need a newer libbpf than the archive ships, compiling gives you control over the build process. That is a real reason to build from source, not a default. For the how, see the walkthrough on compiling libbpf from source, and for what bpftool can actually show you once it runs, the bpftool inspection and debugging guide covers the commands. The github.com/libbpf/bpftool repository serves as an automated upstream mirror for bpftool stand-alone build.

If you are new to writing probes rather than inspecting them, the intro to eBPF with bcc tools is a gentler on-ramp than raw libbpf. For background on the technology itself, the eBPF project site is the primary source.

How do you keep bpftool working across kernel updates?

Install the meta-package so the fix does not silently regress. linux-tools-generic depends on the tools package for the current generic kernel, so when apt upgrade pulls a new kernel, it pulls the matching tools binary in the same transaction. Reboot, and bpftool still finds its backend.

Without the meta-package, every kernel bump quietly strands your pinned linux-tools-$(uname -r) on the old version. It keeps working until you reboot into the new kernel, then breaks with no warning. That delayed failure is the single most common way people "lose" bpftool after an update.

So the durable setup is linux-tools-common plus linux-tools-generic, not a hand-pinned version you have to babysit. Let the meta-package do the tracking. Check with bpftool version after your next reboot into a fresh kernel, and if it comes back clean you are done thinking about this.

FAQ

Does the same fix work for perf and other kernel tools on Noble?

Yes, because they ride the same wrapper. perf, turbostat, and bpftool all live under linux-tools-common and each pulls its real binary from the kernel-versioned tools package. Install linux-tools-$(uname -r) and all of them start working together, since they share one per-kernel package.

Why does 'apt-cache search bpftool' return almost nothing?

Because the name is virtual, so there is no package description text to match. Search the provider instead with apt-cache search linux-tools, or ask apt who satisfies the name using apt-cache showpkg bpftool and read the reverse-provides list. That surfaces linux-tools-common as the thing to install.

Can I run bpftool on a container without touching the host kernel?

Only if the container can see the host kernel's BPF filesystem and has the privileges. bpftool talks to the running kernel through syscalls, so it inspects the host kernel, not the container image. You still need the tools binary matching the host's uname -r, and you need CAP_BPF or root for most operations.

How do I remove a symlink workaround once the real package arrives?

Delete the link, then install the proper package. Run sudo rm /usr/lib/linux-tools/$(uname -r)/bpftool to clear the manual link, then sudo apt install linux-tools-$(uname -r) to lay down the version-correct binary. Confirm with bpftool version and check the path with which bpftool so you know the packaged file is the one in use.

What kernel headers do I need for building eBPF programs on 24.04?

Install linux-headers-generic on a stock Noble system. It provides the header tree your compiler needs and follows the generic kernel line, so it stays aligned across upgrades. If you run a specialized kernel, install the headers package matching that kernel's exact uname -r string instead.