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?