Skip to content
LinuxDeep Dive July 11, 2026 5 min read

Linux Capabilities: Fine-Grained Privileges Beyond root and setuid

How Linux capabilities split root's monolithic power into dozens of independent privileges, and why that's a meaningfully better security model than the traditional all-or-nothing setuid-root pattern.

Traditional Unix privilege checking is binary: a process either runs as UID 0 (root, with essentially unrestricted power over the system) or it doesn’t (subject to normal permission checks). This all-or-nothing model is why so much software historically needed to run entirely as root just to perform one specific privileged operation — binding to a port below 1024, for instance — even though that one operation is a tiny fraction of root’s actual total power. Linux capabilities exist specifically to break that monolith apart.

The core idea: root’s power, split into named pieces

Linux capabilities decompose the traditional “is this UID 0” check into roughly 40 distinct, independently grantable privileges, each covering one specific category of operation that used to require full root. CAP_NET_BIND_SERVICE grants the ability to bind to privileged (sub-1024) ports without granting anything else root could do. CAP_SYS_TIME grants the ability to change the system clock. CAP_NET_ADMIN grants network configuration privileges — a much larger, still meaningfully scoped set. CAP_DAC_OVERRIDE grants the ability to bypass file permission checks entirely, which is closer to root’s full file-access power but still doesn’t grant, say, the ability to load kernel modules or change the system clock.

A process can hold exactly the capabilities it actually needs and nothing more, which is the entire security benefit: a compromised process holding only CAP_NET_BIND_SERVICE and nothing else can bind privileged ports if exploited, but can’t read arbitrary files, can’t load a kernel module, can’t change system time — the blast radius of a successful exploit against that process is bounded by exactly the capability set it was granted, rather than being “full root” by default the way a traditional setuid-root binary’s compromise would be.

Where capabilities actually live: three sets per process

Every process carries three separate capability sets, and understanding the distinction between them is necessary to understand how capabilities actually get applied and inherited:

The permitted set is the ceiling — capabilities the process is allowed to use, whether or not it’s currently using them. The effective set is what’s actually checked against for privileged operations right now — a process can have a capability in its permitted set but temporarily not in its effective set, which is how some programs deliberately drop and re-raise specific capabilities around the exact operations that need them, minimizing the window during which a capability is actually active and exploitable. The inheritable set governs which capabilities survive an execve() call into a new program — critical for how capability-aware programs hand off privileges (or deliberately don’t) when launching subprocesses.

Granting capabilities to a binary directly

Rather than making an entire binary setuid-root and having it drop privileges internally in code (a pattern with a long history of bugs, since it depends entirely on the program correctly dropping privileges at the right moment, in the right order, with no gaps), file capabilities let you grant specific capabilities directly to a binary via extended attributes, with the kernel enforcing that the process only ever gets exactly those capabilities:

setcap cap_net_bind_service=+ep /usr/local/bin/mydaemon

This grants mydaemon the ability to bind privileged ports, in both its permitted and effective sets (+ep), without needing to run as root at all, and without needing any privilege-dropping code inside the binary itself — the kernel is doing the enforcement based on the file’s extended attributes, not trusting the program’s own internal logic to behave correctly.

Capabilities and containers: where this matters enormously in practice

Container runtimes (Docker, Podman, and the underlying runc/crun OCI runtime implementations) lean on capabilities heavily as one of several layers isolating a containerized process from the host. By default, a container is started with a restricted subset of the full capability list — not zero capabilities, but a curated set considered reasonably safe for typical containerized workloads, explicitly excluding capabilities like CAP_SYS_ADMIN (an unusually broad, catch-all capability covering many separate privileged operations that didn’t fit neatly into their own dedicated capability) and CAP_SYS_MODULE (kernel module loading) that would let a compromised container do serious damage to the host if granted unnecessarily.

This is exactly why running a container with --privileged (which grants essentially the full capability set, plus disables several other isolation mechanisms) is a meaningfully different and riskier security posture than running it with the default restricted capability set — --privileged isn’t a performance or convenience flag, it’s specifically removing most of the capability-based isolation that containers rely on as one of their security layers.

Checking what capabilities a running process actually has

/proc/<pid>/status reports a process’s capability sets directly, in a hex-encoded bitmask format:

grep -i cap /proc/self/status

Tools like capsh --decode=<hex-value> translate that bitmask back into human-readable capability names, which is the practical way to actually audit what a specific running process can do beyond squinting at raw hex.

The honest limitation: some capabilities are still very broad

It’s worth being direct that the capability model, while a real improvement over “root or nothing,” isn’t a perfectly fine-grained security boundary in every case. CAP_SYS_ADMIN in particular has accumulated a genuinely large number of loosely-related privileged operations over the kernel’s history — enough that having CAP_SYS_ADMIN alone is functionally close to having most of root’s practical power for many purposes, which somewhat undercuts the “least privilege” ideal the broader capability system is built around. Security-conscious deployments generally treat CAP_SYS_ADMIN with something close to the same caution as full root, rather than assuming “it’s just a capability, not full root” makes it automatically low-risk.