NixOS

This page covers day-to-day use of the services.ncclient module. See NixOS installation for importing the module and its full option list. Paths below use the defaults (stateDir = "/var/lib/ncclient", outputDir = "/var/lib/ncclient/nebula"); adjust them if you changed those options.

What the module sets up

  • ncclient.service: runs ncclient run as root, with nebula from nebulaPackage, plus nftables and iproute2 (for subnet-router/exit-node forwarding and NAT), on its PATH. It restarts on failure.
  • ncclient-enroll.service: only when enrollCodeFile is set. A oneshot that runs ncclient enroll before the main service, only if no token exists yet.
  • The D-Bus policy and polkit rules the Linux desktop app uses, if you also enable services.ncclient-desktop. Only members of adminGroups (default wheel/sudo) can make changes without a password.
  • An ncclient command on the system PATH, pre-pointed at the service’s state. See Running ncclient commands.

Where state lives

PathWhat it is
/var/lib/ncclient/tokenDevice token
/var/lib/ncclient/settings.jsonServer URL, node ID, accepted routes
/var/lib/ncclient/nebula/Nebula config.yaml, certificates, status

Both directories are 0700 root:root.

Enroll

The declarative way is enrollCodeFile: a file holding a one-time code from Nodes → Enroll. For example, a sops-nix secret, or a root-only file you create by hand:

services.ncclient = {
  enable = true;
  server = "https://nebula.example.com";
  enrollCodeFile = "/etc/ncclient/enroll-code";   # or config.sops.secrets.ncclient-enroll.path
};
sudo install -D -m 0600 /dev/stdin /etc/ncclient/enroll-code <<< "XXXXXXXX"
sudo nixos-rebuild switch
journalctl -u ncclient-enroll -u ncclient -b

Once a token exists, the enroll unit does nothing and the code file isn’t read. If the token is ever lost, though, the unit tries the used-up code and fails. ncclient.service requires ncclient-enroll.service, so the main service won’t start either until you re-enroll.

Without enrollCodeFile, enroll with the CLI (below), or with the desktop app’s Enrollment tab.

Run and control

systemctl status ncclient
sudo systemctl restart ncclient
journalctl -u ncclient -f

Change server, interval, or acceptDns in your configuration and nixos-rebuild switch. The unit is restarted with the new values.

Running ncclient commands

The module puts an ncclient wrapper on the system PATH that already points at the service’s stateDir and outputDir. Commands that change the service’s state just need root:

sudo ncclient …

Subnet routes and exit nodes

sudo ncclient routes list
sudo ncclient routes accept 192.168.1.0/24

reject, accept-exit-node --via <IP>, and reject-exit-node work the same way (see the CLI page). The service applies changes on its next poll. With services.ncclient-desktop enabled, you can use the app’s Status tab instead.

Split-horizon DNS

Set services.ncclient.acceptDns = true; and rebuild. The service then runs with --accept-dns and configures the host’s resolver (systemd-resolved is the usual case on NixOS). See Split-horizon DNS on the CLI page for details.

Re-enroll

With enrollCodeFile

The enroll unit only enrolls when there is no token, so remove the old one first:

  1. Get a new code from Nodes → Enroll, and put it in the file enrollCodeFile points at. For a sops-managed secret, update the secret and nixos-rebuild switch. For a plain file, overwrite it.

  2. Remove the old token:

    sudo rm /var/lib/ncclient/token
    
  3. Restart both units:

    sudo systemctl restart ncclient-enroll ncclient
    
  4. Check that it worked:

    journalctl -u ncclient-enroll -u ncclient -b
    

Restarting only ncclient isn’t enough. ncclient-enroll is a oneshot with RemainAfterExit=true, so it stays “active” from boot and isn’t run again.

Keep the old token until you have the new code; once it’s removed the device is offline until enrollment succeeds. If you’re moving to a different server, change server and rebuild in step 1.

Without enrollCodeFile

Enroll with the CLI, pointed at the service’s state (see Running ncclient commands). It overwrites the old token, so there’s nothing to delete:

sudo ncclient --server https://nebula.example.com enroll --code NEWCODE

The service switches to the new token on its next poll. If you changed servers, update server and rebuild, which also restarts the service.

With the desktop app

If services.ncclient-desktop is enabled, the app’s Enrollment tab works as described on the Linux App page, with no password prompt for members of adminGroups.

Updates

The client can check daily for new releases and tell you when one is out (sudo ncclient auto-update enable). On NixOS it never installs anything itself; you update the flake input and rebuild. See Automatic updates.

Troubleshooting

  • ncclient.service won’t start and ncclient-enroll failed: there’s no token, and the code file is missing or its code was rejected (already used or expired). Put a fresh code in the file and restart both units.
  • ncclient: command not found: expected; see Running ncclient commands.
  • Revoked, deleted, or re-enrolled elsewhere - Nebula stopped. Enroll again to reconnect.: the device was revoked, deleted, or re-enrolled elsewhere; Nebula was stopped and its config and key removed. Re-enroll it with a new code.
  • Enrolled with the CLI but the service still waits for enrollment: the CLI ran without both environment variables, so the token went to root’s home directory. Run it again with them.