Linux Capabilities: Fine Grained Privileges Instead of Root
AI generated
$
/etc
Linux · Kernel · Security · System Administration
Linux Capabilities
fine grained privileges beyond root and sudo

Linux capabilities split the traditional root privilege into more than forty individual bits, so a process gets exactly one right, for example binding a privileged port, without inheriting everything else that comes with root. That is the difference between a least privilege design and a server that instantly loses full root access whenever a single compromised service goes wrong.

18 min read setcap · capsh · systemd · containers All common Linux distributions

1. Why "root or nothing" is an outdated model

Traditionally, Linux only knows a binary distinction: a process either runs as root with full access to every privileged operation, or it runs as a regular user and cannot do anything privileged at all. For many real world use cases, this is grossly mismatched. A web server that needs to bind port 80 requires exactly one privileged right for that, but in the classic world it immediately receives full root access to the entire system, including filesystem mounts, kernel module loading and process control over every other user.

Linux capabilities solve exactly this problem by splitting traditional root privileges into more than forty individually grantable abilities. A process using Linux capabilities can be given exactly the one ability it needs, for example binding privileged ports, without inheriting all other root rights. For production servers running PHP-FPM, Nginx or specialized network services, this is the decisive building block for a real least privilege design, where a compromised process does not automatically take over the entire system.

2. How Linux capabilities work: bitmasks instead of root

Technically, the kernel manages several capability sets for each process, represented as bitmasks. The permitted set contains all capabilities a process is generally allowed to hold. The effective set contains the capabilities the kernel actually considers during the current system call check. The inheritable set controls which capabilities can be passed on to a new program on an execve call. This separation allows a process to hold a capability while deliberately keeping it inactive until it is actually needed.

A key difference from SUID binaries: while a SUID program fully switches to the file owner's identity on every start, usually root, Linux capabilities only activate exactly the required bits, while the process identity itself stays unchanged. A program with the CAP_NET_BIND_SERVICE capability can bind privileged ports but has no access whatsoever to other root operations such as setting the system time or loading kernel modules, even if an attacker fully compromises the process.

3. The most important capabilities at a glance

Of the more than forty defined Linux capabilities, only a handful are really relevant for everyday server work. CAP_NET_BIND_SERVICE allows binding ports below 1024, classically used by web servers and mail servers. CAP_NET_ADMIN allows network configuration such as changing routing tables. CAP_SYS_ADMIN is the broadest capability and bundles so many different operations that in practice it behaves almost like root itself, which is why it should be granted with special caution.


# List all capabilities the current shell process holds
capsh --print

# Show the human-readable name of a specific capability bit
capsh --decode=0000000000003000

# List capabilities available on the system with short descriptions
man 7 capabilities | grep -A1 '^       CAP_' | head -40

# Common capabilities relevant to web and database servers:
#   CAP_NET_BIND_SERVICE  — bind ports below 1024 (e.g. port 80/443)
#   CAP_NET_ADMIN         — configure network interfaces and routing
#   CAP_SYS_ADMIN         — broad admin operations, avoid unless required
#   CAP_DAC_OVERRIDE      — bypass file permission checks
#   CAP_CHOWN             — change file ownership regardless of permissions
#   CAP_SETUID/CAP_SETGID  — change process UID/GID

It matters not to grant CAP_SYS_ADMIN as a default fix for "something doesn't work", as unfortunately shown in many Docker tutorials online. This capability covers so many different privileged operations that it is practically almost as risky as full root, undermining the entire point of Linux capabilities as a fine grained privilege model.

4. Assigning capabilities to a program with setcap

The setcap command permanently assigns one or more Linux capabilities to an executable file, stored as an extended filesystem attribute of the file. The classic example: a PHP-FPM or Node.js process that needs to bind port 80 directly, without being started as root.


# Grant a binary the ability to bind privileged ports, nothing else
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node

# Verify which capabilities are attached to a binary
getcap /usr/bin/node
# /usr/bin/node cap_net_bind_service=ep

# Remove a previously assigned capability
sudo setcap -r /usr/bin/node

# Assign multiple capabilities at once, comma-separated
sudo setcap 'cap_net_bind_service,cap_net_raw=+ep' /usr/sbin/my-network-tool

# Run the binary as a non-root user afterwards — port 80 binding still works
sudo -u www-data /usr/bin/node server.js

After the setcap call, the process can run as a normal, unprivileged user and still bind the privileged port. This combination of a non-root process identity and a precisely scoped Linux capability massively reduces the attack surface compared to a classic setup, where the whole process starts as root and privileges have to be laboriously dropped afterwards.

5. Inspecting the capabilities of a running process

For audits and debugging, it matters to see exactly which Linux capabilities an already running process actually holds, independent of what was configured on the binary file via setcap. The kernel exposes this information directly under /proc/PID/status, in four separate hex bitmasks for the different capability sets.


# Inspect capability sets of a running process by PID
grep -i cap /proc/$(pgrep -f nginx | head -1)/status

# Decode the raw hex bitmask into readable capability names
capsh --decode=0000000000002400

# List capabilities for all processes owned by www-data
for pid in $(pgrep -u www-data); do
  echo "PID $pid: $(grep CapEff /proc/$pid/status)"
done

# Compare intended (setcap) vs. actual (runtime) capabilities
getcap /usr/sbin/nginx
grep CapEff /proc/$(pgrep -f 'nginx: master' | head -1)/status

A discrepancy between the capability configured via getcap and the one actually active via /proc/PID/status usually indicates that the process voluntarily dropped the capability at runtime through prctl, a common pattern in security conscious server software that reduces its own privileges after initialization.

6. Restricting capabilities in systemd services

For custom written systemd services, declaring capabilities directly in the unit file is the cleanest way to grant Linux capabilities without manually applying setcap to the binary. systemd offers AmbientCapabilities and CapabilityBoundingSet as two complementary directives for exactly this purpose.


# /etc/systemd/system/my-app.service
[Unit]
Description=Custom application binding a privileged port
After=network.target

[Service]
Type=simple
User=appuser
Group=appuser
ExecStart=/opt/my-app/bin/server

# Grant exactly one capability at process start, without setcap on the binary
AmbientCapabilities=CAP_NET_BIND_SERVICE

# Restrict which capabilities this service could ever gain, even via setuid
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

# Additional hardening commonly paired with capability restrictions
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true

[Install]
WantedBy=multi-user.target

The decisive advantage of this method: CapabilityBoundingSet acts as a hard upper limit for the entire process tree of the service, even if a subordinate component tries to gain additional privileges through other means. Combined with NoNewPrivileges, this prevents the service from ever acquiring more Linux capabilities than explicitly declared in the unit file, regardless of what the executed program itself attempts.

7. Capabilities in containers: Docker and the default list

Container runtimes such as Docker start containers by default with a restricted selection of Linux capabilities, not with full root, even if the process inside the container runs as UID 0. This default list covers roughly 14 capabilities, significantly fewer than the more than forty available, and can be both restricted further and specifically extended.

For most web application containers, even Docker's default list is still too generous. A PHP-FPM or Node.js container that needs no network configuration and no filesystem permission changes should start with --cap-drop=ALL and then only add back the actually required Linux capabilities specifically via --cap-add. This pattern significantly reduces the attack surface of a compromised container, because even a break out of the process context does not automatically bring far reaching system privileges along.

8. Common mistakes and debugging capability problems

The most common mistake when working with Linux capabilities is to misinterpret a failed privileged operation as a general permission problem and then reflexively grant CAP_SYS_ADMIN or even full root, instead of identifying the specific missing capability. The kernel log via dmesg or audit logs, with auditing enabled, show exactly which capability was missing for which system call.


# Enable auditd rules to log capability check failures
sudo auditctl -a always,exit -F arch=b64 -S capset -k cap_debug

# Search recent audit log for capability-related denials
sudo ausearch -k cap_debug --start recent

# Trace which capability a process needs using strace on the failing syscall
strace -f -e trace=%network ./my-app 2>&1 | grep -i "eperm\|eacces"

# Common mistake: file was copied/rebuilt after setcap, losing the attribute
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node
cp /usr/bin/node /usr/bin/node.bak   # WRONG: cp does not preserve xattrs by default
cp --preserve=all /usr/bin/node /usr/bin/node.bak   # RIGHT: preserves capability xattr

Another frequently overlooked pitfall: capabilities are stored as an extended filesystem attribute and get lost on a plain cp without --preserve=all, on a deployment via rsync without -X, or when a Docker image is rebuilt if the setcap assignment is not part of the build process. Anyone using Linux capabilities in a CI/CD pipeline should therefore include the setcap step explicitly in the deployment script, instead of relying on a one time manually set capability.

9. Capabilities compared to SUID, sudo and full root

There are several established mechanisms for giving a process extended privileges, differing significantly in granularity and risk. Linux capabilities are usually the most precise and safest option for server services.

Mechanism Granularity Risk if compromised Typical use
Linux capabilities Very fine (bit level) Low, precisely bounded Server services, containers, systemd
SUID bit Coarse (full owner context) High, often full root context Legacy tools like passwd, ping
sudo Medium (command level) Depends on sudoers rules Interactive administration
Full root None Maximum Only when truly unavoidable
SELinux/AppArmor (complementary) Very fine (rule based) Low, but more complex to maintain Additional hardening alongside capabilities

In practice, these mechanisms do not exclude each other. A well hardened server combines Linux capabilities for precise process privileges, sudo for controlled interactive administration, and optionally SELinux or AppArmor as an additional layer that further restricts, even within a granted capability, which files or network destinations are reachable.

Mironsoft

Server hardening, least privilege design and container security

Is everything still running as root at your place?

We analyze your server services and containers, replace unnecessary root and SUID privileges with precisely granted Linux capabilities, and harden systemd services with CapabilityBoundingSet.

Capability audit

Analyzing and documenting existing root and SUID usage

Least privilege redesign

Applying setcap, systemd directives and container cap drop deliberately

CI/CD integration

Anchoring capability assignment reproducibly in deployment pipelines

10. Summary

Linux capabilities replace the crude root or nothing model with more than forty individually grantable rights, so a process can perform exactly the privileged operation it needs without inheriting the rest of root. setcap permanently assigns these rights to a binary, capsh and /proc/PID/status make visible which capabilities a process actually holds, and systemd directives such as AmbientCapabilities and CapabilityBoundingSet integrate this model directly into your own services.

The biggest lever is consistently using Linux capabilities instead of SUID bits or blanket root starts, both for classic server services and in containers with --cap-drop=ALL as a starting point. Anyone who maintains this discipline significantly reduces the attack surface of every single service without having to give up the functionality that is actually needed.

Linux Capabilities — The Essentials at a Glance

Principle

Root privileges are split into more than forty individually grantable bits instead of all or nothing.

setcap

setcap 'cap_net_bind_service=+ep' binary grants exactly one ability, without a root start.

systemd

CapabilityBoundingSet hard-caps which capabilities a service could ever acquire.

Containers

--cap-drop=ALL plus targeted --cap-add minimizes the container's attack surface.

11. FAQ: Linux Capabilities

1What are Linux capabilities?
Individually grantable privileges that split root into more than forty separate bits instead of all or nothing.
2What does setcap do?
Permanently assigns capabilities to a file as a filesystem attribute, without needing root execution.
3Difference from SUID?
SUID switches fully to the owner identity, capabilities grant only exactly needed individual rights.
4Checking a process's capabilities?
capsh --print or read /proc/PID/status, with capsh --decode for readable names.
5Why avoid CAP_SYS_ADMIN?
Bundles so many operations it practically behaves like full root and undermines least privilege.
6Setting capabilities in systemd?
AmbientCapabilities for granted rights, CapabilityBoundingSet as a hard upper limit.
7Docker containers with all capabilities?
No, Docker starts with a restricted list of about 14 default capabilities.
8Why does a capability disappear after a copy?
Stored as a filesystem attribute, cp without --preserve=all or rsync without -X won't copy it.
9Finding a missing capability?
Use an auditd rule for capset calls and ausearch, instead of granting more rights across the board.
10Alternative to SELinux/AppArmor?
More of a complement: capabilities limit operation types, SELinux/AppArmor additionally limit targets.