Skip to main content

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.

note

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

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
warning

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.