Running Nebula as a non-root user
Nebula is commonly run as root, but on Linux the privileged operations it performs are creating the tun device,
configuring its address and MTU, and installing routes are all covered by the CAP_NET_ADMIN
capability, so Nebula can run as a dedicated unprivileged
user if the process is granted that capability. Running this way limits the impact of a compromise or bug in Nebula to
what CAP_NET_ADMIN allows, rather than full root.
Everything else Nebula does is unprivileged, with one exception: binding a listener to a port below 1024 additionally
requires CAP_NET_BIND_SERVICE. This applies to any of Nebula's listeners you configure on a privileged port, e.g.
Lighthouse DNS on the default DNS port 53, the main UDP listener (e.g.
listen.port: 443 to traverse restrictive networks), the Prometheus stats endpoint, or the debug
sshd. Outbound-only features such as Graphite stats need nothing extra.
This only applies to Linux. On macOS and Windows, creating the tun device requires root/Administrator, and there is no
equivalent capability mechanism. If you only need a lighthouse or relay on those platforms, you can set
tun.disabled: true to run without a tun device (and therefore without root).
Create a user for Nebula
Create a system user and give it ownership of the config directory. The private key and CA material should not be readable by other users:
sudo useradd --system --shell /usr/sbin/nologin nebula
sudo chown -R nebula:nebula /etc/nebula
sudo chmod 0700 /etc/nebula
Option 1: systemd ambient capabilities (recommended)
If you manage Nebula with systemd, add User, Group, and the two capability directives to the [Service] section of
your unit. This is the
example unit from the Nebula
repository with those four lines added:
[Unit]
Description=Nebula overlay networking tool
Wants=basic.target network-online.target nss-lookup.target time-sync.target
After=basic.target network.target network-online.target
Before=sshd.service
[Service]
Type=notify
NotifyAccess=main
SyslogIdentifier=nebula
User=nebula
Group=nebula
CapabilityBoundingSet=CAP_NET_ADMIN
AmbientCapabilities=CAP_NET_ADMIN
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/local/bin/nebula -config /etc/nebula/config.yml
Restart=always
[Install]
WantedBy=multi-user.target
AmbientCapabilities is what grants CAP_NET_ADMIN to the non-root process. CapabilityBoundingSet additionally drops
every other capability from the service, so even a compromised process cannot regain them.
Reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart nebula
If any listener (Lighthouse DNS, the main UDP port, stats, sshd) binds below port 1024, list both capabilities:
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
Option 2: file capabilities with setcap
If you are not using systemd, you can attach the capability to the binary itself and run it directly as the unprivileged user:
sudo setcap cap_net_admin+ep /usr/local/bin/nebula
If any listener binds below port 1024:
sudo setcap 'cap_net_admin,cap_net_bind_service+ep' /usr/local/bin/nebula
File capabilities apply to anyone who can execute the binary — any local user could then create tun devices and modify routes by running it. Prefer the systemd approach where possible, or restrict execute permission on the binary to the nebula user. Note that the capability is attached to the file's extended attributes, so it must be re-applied after upgrading or replacing the binary.
Verifying
Check that the process is running as the nebula user with only the expected capabilities:
$ ps -o user,cmd -C nebula
USER CMD
nebula /usr/local/bin/nebula -config /etc/nebula/config.yml
$ grep CapEff /proc/$(pidof nebula)/status
CapEff: 0000000000001000
0000000000001000 decodes to exactly cap_net_admin (capsh --decode=0000000000001000); with CAP_NET_BIND_SERVICE
added it is 0000000000001400. Confirm the interface and any unsafe_routes are present
with ip link show nebula1 and ip route.
Troubleshooting
Failed to get a tun/tap device ... operation not permitted — the process does not have CAP_NET_ADMIN. Confirm
the unit's AmbientCapabilities line (or getcap output on the binary) and that you restarted the service after
systemctl daemon-reload. Nebula exits immediately on this error.
bind: permission denied — a listener (here Lighthouse DNS:
Failed to start server: listen udp 0.0.0.0:53: bind: permission denied) is configured on a privileged port but the
process only has CAP_NET_ADMIN; add CAP_NET_BIND_SERVICE. Note that for the DNS responder this error is non-fatal:
the service stays running with a working tun device and only the DNS responder is down, so watch the logs rather than
the service state.
bind: address already in use on port 53 — the capability is working, but something else already owns the port. On
distributions with systemd-resolved, its stub listener on 127.0.0.53:53 conflicts with binding 0.0.0.0:53; bind
Nebula's DNS to a specific address or disable the stub listener.
Failed to set tun tx queue length: operation not permitted — logged when Nebula runs inside a container
(LXC/Incus/Docker) where the tx queue length cannot be changed even with CAP_NET_ADMIN. It is harmless; the interface
still comes up.