I'm trying to run nebula (an overlay networking tool) as a systemd user service, NOT as a system service with user and group defined in the unit file. All examples about running a service with CAP_NET_ADMIN as a non-root user I have found, do it as a system service with specific user and group defined in the unit file (example 1, non-nebula CAP_NET_ADMIN example 2). I have not tried doing it that way but I have no doubt that it would work as there are numerous articles and guides about setting it up that way.
I'm running Debian 13 with systemd 257 (257.7-1) and nebula 1.9.3.
I've created the unit file based on the example 1.
$ cat ~/.config/systemd/user/nebula.service [Unit] Description=nebula [Service] WorkingDirectory=~ AmbientCapabilities=CAP_NET_ADMIN Type=exec LimitNOFILE=65535 ExecStart=nebula -config .config/nebula/config.yml KillSignal=SIGINT StandardOutput=journal StandardError=journal [Install] WantedBy=default.target Below is the result after running systemctl --user daemon-reload && systemctl --user start nebula and journalctl --user -u nebula
Starting nebula.service - nebula... nebula.service: Failed to apply ambient capabilities (before UID change): Operation not permitted nebula.service: Failed at step CAPABILITIES spawning nebula: Operation not permitted nebula.service: Main process exited, code=exited, status=218/CAPABILITIES nebula.service: Failed with result 'exit-code'. Failed to start nebula.service - nebula. systemd.exec manual has the following disclaimer for AmbientCapabilities option, which is required to set the CAP_NET_ADMIN to allow the service to create tun/tap interface:
These options are only available for system services, or for services running in per-user instances of the service manager in which case PrivateUsers= is implicitly enabled (requires unprivileged user namespaces support to be enabled in the kernel via the "kernel.unprivileged_userns_clone=" sysctl).
If I understand it correctly, my use case falls under the "services running in per-user instances of the service manager" and unprivileged user namespaces support is enabled in my system (I didn't touch this option, it appears to be default configuration in Debian 13):
$ /sbin/sysctl kernel.unprivileged_userns_clone kernel.unprivileged_userns_clone = 1 I don't know anything about user namespaces, but since the manual mentions the PrivateUsers option I tried to play around with all possible values in the unit file.
PrivateUsers=self (same result when it is set to true)
Starting nebula.service - nebula... Started nebula.service - nebula. time="2025-09-04T21:45:39+03:00" level=info msg="Firewall rule added" firewallRule="map[caName: caSha: direction:outgoing endPort:0 groups:[] host:any ip: localIp: proto:0 startPort:0]" time="2025-09-04T21:45:39+03:00" level=info msg="Firewall rule added" firewallRule="map[caName: caSha: direction:incoming endPort:0 groups:[] host:any ip: localIp: proto:1 startPort:0]" time="2025-09-04T21:45:39+03:00" level=info msg="Firewall started" firewallHashes="*XXX*,FNV:*XXX*" time="2025-09-04T21:45:39+03:00" level=error msg="Failed to get a tun/tap device" error="operation not permitted" nebula.service: Main process exited, code=exited, status=1/FAILURE nebula.service: Failed with result 'exit-code'. PrivateUsers=identity
Starting nebula.service - nebula... nebula.service: Failed to set up user namespacing for unprivileged user: Operation not permitted nebula.service: Failed at step USER spawning nebula: Operation not permitted nebula.service: Main process exited, code=exited, status=217/USER nebula.service: Failed with result 'exit-code'. Failed to start nebula.service - nebula. Then I tried to run capsh instead of nebula as it appears to show what capabilities the process is actually getting using the following settings in the same unit file:
PrivateUsers=self ExecStart=/sbin/capsh --print journalctl log for this run:
Starting nebula.service - nebula... Started nebula.service - nebula. Current: cap_net_admin=eip Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,cap_perfmon,cap_bpf,cap_checkpoint_restore Ambient set =cap_net_admin Current IAB: ^cap_net_admin Securebits: 00/0x0/1'b0 (no-new-privs=0) secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: no (unlocked) secure-no-ambient-raise: no (unlocked) uid=60198(XXX) euid=60198(XXX) gid=60198(XXX) groups=60198(XXX) Guessed mode: HYBRID (4) It appears that when PrivateUsers=self it successfully sets the CAP_NET_ADMIN flag for the process, however, nebula is still unable to create the tun/tap device.
To rule out the possibility of nebula-specific issue, I also tried the following
PrivateUsers=self ExecStart=strace ip tuntap add mode tap tap0 Still, operation not permitted.
Started nebula.service - nebula. ... capget({version=_LINUX_CAPABILITY_VERSION_3, pid=0}, NULL) = 0 capget({version=_LINUX_CAPABILITY_VERSION_3, pid=0}, {effective=1<<CAP_NET_ADMIN, permitted=1<<CAP_NET_ADMIN, inheritable=1<<CAP_NET_ADMIN}) = 0 ioctl(1, TCGETS, 0x7ffe55fda310) = -1 ENOTTY (Inappropriate ioctl for device) socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 3 setsockopt(3, SOL_SOCKET, SO_SNDBUF, [32768], 4) = 0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 setsockopt(3, SOL_NETLINK, NETLINK_EXT_ACK, [1], 4) = 0 bind(3, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 0 getsockname(3, {sa_family=AF_NETLINK, nl_pid=128596, nl_groups=00000000}, [12]) = 0 setsockopt(3, SOL_NETLINK, NETLINK_GET_STRICT_CHK, [1], 4) = 0 openat(AT_FDCWD, "/dev/net/tun", O_RDWR) = 4 ioctl(4, TUNSETIFF, 0x7ffe55fda330) = -1 EPERM (Operation not permitted) ... ioctl(TUNSETIFF): Operation not permitted nebula.service: Main process exited, code=exited, status=1/FAILURE nebula.service: Failed with result 'exit-code'. Am I missing anything here? Is it supposed to work at all?