Safeguard
Security Guides•

Bind to port 80 without running as root: Linux capabilities

Give a service only CAP_NET_BIND_SERVICE instead of full root. How capabilities split root powers, and the systemd settings that apply them safely.

Safeguard Security Team
3 min read

A service does not need to run as root to listen on port 80 or 443. The Linux capability CAP_NET_BIND_SERVICE allows binding to ports below 1024 and nothing else. With systemd, run the service as a normal user and grant only that capability through AmbientCapabilities=.

What capabilities are

The capabilities(7) manual explains the old model: a process with effective user ID 0 bypasses all kernel permission checks, while other processes face full checks. Since Linux 2.2, the powers of the superuser have been divided into units called capabilities, which can be enabled and disabled independently. They are a per-thread attribute.

That means a program can hold one narrow power instead of all of them.

The one you need for low ports

The manual describes CAP_NET_BIND_SERVICE as permission to bind a socket to privileged Internet domain ports, meaning port numbers below 1024. It is a good example of how targeted a capability can be.

Two ways to grant it

On the unit, with systemd (usually best):

[Service]
User=webapp
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes

The systemd.exec manual says ambient capabilities are useful when you want to run a process as a non-privileged user but still give it some capabilities. CapabilityBoundingSet= limits what the process can ever hold. The manual notes that if you do not set it, the bounding set is not modified and no limit is enforced. Adding NoNewPrivileges=yes stops the process and its children from gaining more through execve().

On the binary, with setcap: since Linux 2.6.24 the kernel can attach capability sets to an executable file, stored in an extended attribute, using setcap. Writing it needs the CAP_SETFCAP capability. This works outside systemd, but it ties the privilege to the file, so anyone able to replace or copy that binary context needs watching. Check with getcap after every package update, since replacing the file can drop the attribute.

Capabilities to be wary of

The manual is blunt about CAP_SYS_ADMIN: it says to avoid it if you possibly can, because a vast proportion of capability checks sit behind it and it can plausibly be called "the new root". If a vendor tells you a service needs it, ask which exact feature, and look for a narrower choice.

A short review routine

  1. List which of your services run as root only to bind a port.
  2. Move each to a normal user plus the one capability.
  3. Set a bounding set so extras cannot appear later.
  4. Confirm the result by reading /proc/<pid>/status and looking at the Cap lines, or with a tool such as capsh.
  5. Re-run the systemd exposure check and note the improvement.

If you terminate TLS in a proxy, another route is to let the proxy hold the low port capability and keep your app on a high port, with no capabilities at all.

Safeguard works on the supply chain side of web services: SBOM and dependency scanning, reachability analysis and a free scan at gold.safeguard.sh/scan.

Quick check

Which capability allows binding to ports below 1024?

CAP_NET_BIND_SERVICE.

Why avoid CAP_SYS_ADMIN?

The capabilities manual says it covers a vast range of checks and can plausibly be called the new root, so granting it is close to granting root.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.