Traditionally, a Linux process was either root (allowed to do everything) or not. Capabilities split root’s power into about 40 separate privileges. A program can get just the one it needs, for example permission to bind to port 80, without becoming all-powerful. That’s a simple, effective way to reduce the damage a compromised service can do.

Common capabilities

Capability Allows
CAP_NET_BIND_SERVICE Binding to ports below 1024 (80, 443, 53…)
CAP_NET_ADMIN Configuring interfaces, routes, firewall rules
CAP_NET_RAW Raw sockets (ping, packet capture)
CAP_SYS_ADMIN A huge grab bag (mounts and much more), nearly root. Avoid
CAP_SYS_PTRACE Tracing/debugging other processes
CAP_CHOWN, CAP_DAC_OVERRIDE Change ownership; bypass file permission checks
CAP_SYS_TIME Set the system clock
CAP_SETUID / CAP_SETGID Change user/group IDs

Full list: man 7 capabilities.

Example: a Node.js or Go app on port 443 without root

Option 1: file capabilities on the binary:

sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/myserver
getcap /usr/local/bin/myserver
# /usr/local/bin/myserver cap_net_bind_service=ep

Now any user running myserver can bind to ports below 1024. Notes:

  • Capabilities are lost when the file is replaced (for example on upgrade). Re-apply them.
  • Setting it on a shared interpreter (/usr/bin/node, /usr/bin/python3) gives the capability to every script using that interpreter. Prefer option 2 for interpreters.

Option 2 (recommended): grant it in the systemd unit:

[Service]
User=myapp
Group=myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

The service runs as an unprivileged user, with exactly one extra privilege. See systemd unit files.

Alternative: keep the app on a high port (3000), and put Nginx in front on 80/443. That’s the most common setup anyway. See Nginx reverse proxy.

Inspect capabilities

getcap -r /usr/bin /usr/sbin /usr/local/bin 2>/dev/null     # files with capabilities
grep Cap /proc/$(pgrep -o nginx)/status                    # a process's capability sets (hex)
capsh --decode=00000000a80425fb                            # decode a hex mask
pscap                                                      # from libcap-ng-utils: capabilities of running processes

Unexpected file capabilities (for example cap_setuid on a random binary) can be a privilege-escalation backdoor. Review them as part of security checks. See detect a compromised server.

Harden services by dropping capabilities

Even services that start as root can drop what they don’t need:

[Service]
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

systemd-analyze security myapp.service scores a unit’s hardening, and suggests options.

Containers: drop by default

Docker gives containers a reduced default set. Tighten it further:

services:
  web:
    image: myapp
    cap_drop: [ALL]
    cap_add: [NET_BIND_SERVICE]
    security_opt:
      - no-new-privileges:true

Avoid privileged: true, which grants everything, including CAP_SYS_ADMIN. See Linux namespaces and Kubernetes security basics.

Why it’s worth the effort

Most service compromises start with a bug in the application: a vulnerable plugin, an injection flaw, or a deserialisation issue. What the attacker can do next depends on the privileges of the process they took over. A web app running as root hands over the whole server. The same app running as an unprivileged user, with only CAP_NET_BIND_SERVICE, can bind ports, and little else. Combined with NoNewPrivileges, it can’t gain more, even through setuid programs.

Quick reference

Task Command
Give a binary a capability setcap 'cap_net_bind_service=+ep' /path/bin
Show file capabilities getcap /path/bin
Remove setcap -r /path/bin
systemd service AmbientCapabilities= + CapabilityBoundingSet=
Docker cap_drop: [ALL] + cap_add: […]
Audit a unit systemd-analyze security <unit>

Related: Linux cgroups and Linux server hardening checklist.

  • Linux
  • Security
  • systemd