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, plus RuntimeDirectory for the control socket (see Using nebula ctl):

[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
RuntimeDirectory=nebula
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.

Using nebula ctl​

Nebula's local control socket defaults to /var/run/nebula/ctl.sock, and an unprivileged Nebula cannot create /var/run/nebula itself. With systemd, RuntimeDirectory=nebula in the unit above creates /run/nebula owned by the nebula user each time the service starts. Without systemd, create the directory yourself, owned by the nebula user and not writable by anyone else, or set local_api.control.socket to a path in a directory the nebula user owns.

Then run nebula ctl as root, or as the nebula user:

sudo nebula ctl list-hostmap
sudo -u nebula nebula ctl list-hostmap

To let other users run it, set local_api.control.group. If the socket is not at the default path, pass -config /etc/nebula/config.yml so nebula ctl can find it; since /etc/nebula is only readable by the nebula user above, run it as root or as the nebula user.

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.

Could not start the local control socket — Nebula could not create the control socket, usually because the nebula user cannot create /var/run/nebula. See Using nebula ctl. This is not fatal: Nebula keeps running without the control socket, and tries again on every reload.

could not set tun device name: nebula1 because device or resource busy — an interface with the tun.dev name already exists, either a persistent tun device created ahead of time or one held by another running Nebula. Nebula creates the device itself, so remove the existing one or pick another name.

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.