A VPN daemon needs privileged network operations even though ordinary applications open outgoing sockets without any privilege at all. That gap is where most capability mistakes start: somebody sees a permission error, grants the whole set, and the service works for reasons nobody can explain later. The walkthrough below starts from the failed operation, maps it to one authority, and then checks the context the process actually runs in. Running as unrestricted root is a valid answer for some deployments, but it should be a decision rather than a default.

Start from the operation that failed

Read the error before you read a tutorial. "Operation not permitted" on an interface change, a refusal to open a device node, and a bind failure on a low port are three different problems with three different grants. If the process starts and only fails at the first route change, that also tells you the interface creation already succeeded, which narrows the answer considerably.

Reproduce the failure with the service stopped, then start it in the foreground under the same user if the implementation allows it. An error you can see beats a log line you have to guess at, and it makes the difference between a capability problem and an SELinux or AppArmor denial obvious.

Map each operation to one authority

The table is a starting map, not a substitute for the implementation's documentation:

OperationUsual authorityWatch for
Create or configure tun/tapCAP_NET_ADMIN in the relevant namespaceCreating the device and opening it are separate steps
Add routes, rules, firewall entriesCAP_NET_ADMINSame capability, different namespace than you think
Open /dev/net/tunDevice permissions, not a capabilityGroup membership or a udev rule may be enough
Raw socketsCAP_NET_RAWNeeded by some user-space tunnel implementations
Bind a port below 1024CAP_NET_BIND_SERVICEIrrelevant for most modern tunnel ports

Two rows in that table are the ones people conflate. Opening /dev/net/tun is a file-permission question; asking the kernel to configure what you opened is a capability question. Granting CAP_NET_ADMIN does not fix a device node your service user cannot read, and fixing the node does not give you route authority. Check both before you change either.

Kernel WireGuard and user-space tunnel programs do not have identical requirements either. The kernel module path configures an existing interface through netlink, while a user-space implementation may also need the device and, for some transports, raw sockets.

Read the context: service, container or shell

A capability in the host's network namespace can affect far more than the same operation inside a deliberately isolated stack. A container runtime typically starts with a reduced bounding set; adding NET_ADMIN to a container that shares the host network is a much larger grant than adding it to one with its own network namespace and no host mounts.

For systemd units, read what is actually applied rather than what the unit file appears to say: systemctl show -p CapabilityBoundingSet -p AmbientCapabilities reports the effective values, which may be set by a drop-in you forgot. Ask for the effective value, never the intended one. Ambient capabilities matter because a non-root service loses capabilities across exec without them.

Use the package or image's supported setup as the baseline. Do not add raw-socket permissions or privileged mode because another tutorial listed them for another tool; that reasoning transfers nothing and costs you the isolation you paid for. When in doubt, grant nothing and read the implementation notes for the exact operation you need.

Watch what happens after privileges drop

Many daemons start privileged, configure the interface, then drop to an unprivileged user and keep the tunnel. From that point the process can no longer add a route, rewrite a firewall rule or open a new privileged device, which is fine until a reconnect needs exactly that. Later file reads and hook execution can fail for the same reason.

Test the whole lifecycle rather than the first second: startup, a renewal or reconnect, and cleanup on stop. A first successful interface creation does not establish that every later privileged operation will still work, and the failure usually appears hours later in production.

Implementations that keep the tunnel open across a user switch usually pair that with options to persist the tunnel and keys. On the systemd side, the equivalent question is whether the unit's capability set survives the user switch you configured.

Record the grant and its reason

Write down the exact grant, the operation it enables and the error it fixed. One line in the deployment notes — "CAP_NET_ADMIN in the unit's network namespace, needed for route changes after reconnect" — is enough to stop the next person from widening the grant again.

Then re-read the effective set after every upgrade. Vendors add features, images change defaults, and a capability that was unnecessary last year can become load-bearing without anyone noticing.

Inyandiko zihuye