A service that runs as an unprivileged user may still need one Linux capability. AmbientCapabilities= can pass that capability across execve(). NoNewPrivileges=yes is often added beside it as a hardening rule. The combination is useful, but the two settings answer different questions.

What ambient capabilities do

Capabilities normally depend on the executable, the process's permitted and inheritable sets, and the mechanism that started it. Ambient capabilities make selected capabilities survive an ordinary execute call. The systemd manual explains that adding a capability to the ambient set also adds it to the process's inherited set.

When systemd changes the user for the unit, it automatically enables keep-caps in SecureBits= so the permissions are retained across the user change. That is helpful for a service that needs CAP_NET_BIND_SERVICE to bind a low port, but it also means the capability is present in the running process.

The allowlist should be reviewed as an authority grant. A service that needs to bind port 443 does not automatically need CAP_NET_RAW, CAP_SYS_PTRACE, or CAP_DAC_OVERRIDE. Start with a full capability list for the service, then compare it with the capabilities that are actually used during startup, steady state, and cleanup.

What NoNewPrivileges blocks

NoNewPrivileges=yes prevents the service and its children from gaining new privileges through execve(). That includes setuid or setgid bits and filesystem capabilities on a newly executed file. It is not a revocation of the process's current capabilities.

The setting also has a scope limit. systemd notes that it affects processes in the unit's subtree, but not processes requested through tools such as at, crontab, systemd-run, or another IPC service. Those jobs have their own execution context and must be secured separately.

For a service that already has an ambient capability, NoNewPrivileges=yes can prevent a child from acquiring a second capability from an executable file. The service still has the ambient capability that was granted at startup. Review both the current authority and the paths that could acquire more.

Use a complete capability policy

The following pattern is a starting point for a service that needs one capability and nothing else:

[Service]
User=app
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
RestrictSUIDSGID=yes
LockPersonality=yes
SystemCallArchitectures=native

Every setting must match the application. CapabilityBoundingSet= removes capabilities from the set that can be gained. AmbientCapabilities= selects the one that should survive execution. RestrictSUIDSGID=yes prevents the creation or use of setuid or setgid files in the namespace. A service that shells out to a helper with different needs requires a separate unit or a carefully bounded command path.

The + command prefix is another review point. The systemd manual notes that AmbientCapabilities= does not affect commands prefixed with +. If the service command uses that prefix, the sandbox assumptions around ExecStart= need to be tested separately.

Verify from inside the unit

Use a nonproduction host or a transient unit to inspect the effective sets. The service account should see the approved ambient capability and no others. A child process started by the service should retain only the intended set. A command that should be blocked by NoNewPrivileges should fail to gain a file capability.

Review the kernel and systemd audit evidence together:

systemctl show example.service -p CapabilityBoundingSet -p AmbientCapabilities -p NoNewPrivileges
capsh --print
systemd-analyze security example.service

capsh --print must run inside the service's context. Running it as the administrator outside the unit proves nothing about the unit's sets.

Keep helpers and scheduled jobs in scope

Ambient capabilities can leak into a child process that was not part of the design review. A shell, a plugin, or a maintenance command may inherit the capability and use it in a different context. Use ExecStart= with a single executable where possible, and avoid passing user-controlled command strings through a shell.

The combination of AmbientCapabilities= and NoNewPrivileges=yes is not a binary hardening check. It is a statement of which capability the service may retain and which acquisition paths should fail. Test the statement, record the result, and retest it when the service, executable, or helper changes.

References