The service starts, the log complains that it cannot open the tun device, and routes never appear. Run capsh --print from your shell and cap_net_admin is right there, which makes the failure look impossible. What differs between your shell and the unit is the privilege transition, not the kernel's willingness to grant the capability. Work through the four checks below in order and stop at the first one that explains the gap.

Separate a missing capability from a hidden device

Two failures look alike from the log line. A process without CAP_NET_ADMIN cannot create or configure the tunnel interface, and a process whose sandbox hides /dev/net/tun cannot open it in the first place. Adding capabilities will not fix the second one.

Check whether the device exists inside the service's namespace before you edit anything. PrivateDevices=yes is a common hardening line that removes the tun node entirely, and a restrictive ProtectSystem can make the configuration path read-only in a way that reads like a permission error. Both produce log lines that mention the tunnel, which is why they get blamed on capabilities. Confirm the node is present and openable first, then move on.

Know which capability set you are looking at

Linux keeps permitted, effective, inheritable, bounding and ambient sets, and the ambient set is the one that survives an exec by a non-root user. File capabilities on the binary can hand over permitted and effective rights at exec, but only while nothing in the chain blocks that transfer.

This is where sudo misleads people. A command run through sudo -u starts a fresh privilege transition and the ambient set is gone, so the same check that succeeds as root fails there. The service manager is another transition again, and it grants nothing ambient unless the unit says so.

Grant the capability in the unit, not in a wrapper

A minimal service section for a tunnel that needs to create a device and install routes reads like this.

[Unit]
Description=OpenVPN client, office profile
After=network-online.target

[Service]
Type=notify
User=openvpn
Group=openvpn
NoNewPrivileges=yes
AmbientCapabilities=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN
ExecStart=/usr/sbin/openvpn --config /etc/openvpn/office.conf

AmbientCapabilities puts the right into the ambient set before the process starts, and CapabilityBoundingSet keeps the process from holding anything outside that list. NoNewPrivileges=yes blocks privilege gain through setuid or file capabilities, while the capability the unit grants explicitly still applies. Add CAP_NET_BIND_SERVICE only when the service must bind a low port, and never add CAP_SYS_ADMIN because a forum thread did.

If the unit wraps the binary in sudo, remove the wrapper. systemd already applies User=, and that extra layer is the most likely reason the ambient set is empty.

Verify against the live process, not the unit file

Read what the manager actually applied, then read the process it spawned.

unit=openvpn-client@office
systemctl show -p User -p AmbientCapabilities "$unit"
systemctl show -p MainPID --value "$unit" | xargs -r getpcaps
grep -E '^Cap(Eff|Amb)' /proc/$(systemctl show -p MainPID --value "$unit")/status

If the unit lists the capability and CapEff still lacks it, a drop-in is overriding the setting or an ExecStartPre step is changing the environment. Search the drop-in directories for the unit name before you assume the main file is authoritative, because a later file wins silently. If both the unit and the process show the capability and the tunnel still fails, go back to the device and path checks rather than widening the bounding set. Widening it hides the real cause and leaves the service more privileged than it was.

Prefer dropping privileges after startup

OpenVPN can start privileged and then drop to an unprivileged account through its own user and group options, with persist-key and persist-tun keeping the tunnel usable afterwards. That arrangement narrows the window in which the process holds rights, and it is often a better fit than starting the whole daemon unprivileged with permanent capabilities.

Whichever model you pick, restart the unit and repeat the checks after a reboot, because a service that works from an interactive root shell and fails at boot is usually a capability, device or file-permission difference. Record the unit settings next to the process capability output so the next reviewer can see whether a package update or a drop-in moved the boundary.

Related articles