Subnet Routers and Exit Nodes
Nebula Commander can turn a node into a subnet router (it advertises a LAN behind
it to the rest of the mesh, the way tailscale up --advertise-routes does) or an
exit node (it advertises 0.0.0.0/0/::/0 and routes all of another node’s
traffic, the way tailscale up --advertise-exit-node does). Both build on Nebula’s
own unsafe_routes
feature.
This covers how it’s configured from both sides — the gateway that advertises a
route, and the other nodes that actually use it — what happens automatically for
nodes running ncclient on Linux, and what you have to do
yourself for everything else (Windows, macOS, Docker-deployed ncclient, or bare
nebula).
Two sides of the same setting
Every route has a gateway (the node advertising it) and one or more consumers (the nodes that actually route through it). Nebula Commander exposes both sides in the Nodes page’s node-details panel:
- On the gateway node, under Advanced → Subnet Router & Exit Node Config (and Advanced → Exit Node), you choose what this node advertises and, per route, a “Used by” checklist of which other nodes are allowed to consume it.
- On a consumer node, the visible (non-Advanced) Use Subnet Router and Use Exit Node dropdowns let you pick a gateway directly, without opening the gateway’s own settings — the same underlying relationship, edited from whichever side is more convenient at the time.
Both mechanisms write to the same data: picking a gateway from a consumer’s dropdown adds that consumer to the gateway’s “Used by” list for every matching route, and clears it from any other gateway’s routes of the same kind (a node uses at most one subnet router and one exit node at a time). Checking a node in a gateway’s “Used by” list does the same thing in reverse. Either way, a route reaches nobody until an admin explicitly says who it’s for — advertising a route is never enough on its own.
Setting up a route (gateway side)
In a node’s details panel (Nodes → hostname → Edit), expand Advanced:
- Exit Node — a single checkbox, Exit node (route all traffic). Checking it
advertises both
0.0.0.0/0and::/0from this node. - Subnet Router & Exit Node Config → Advertised subnets — a checklist of local
interfaces
ncclientdiscovered on this node (ethernet, Wi-Fi, Tailscale, or another Nebula interface on the same host; Docker interfaces are never offered). Only populated for nodes actively runningncclienton Linux. - Subnet Router & Exit Node Config → Other — type any CIDR by hand. Use this for
a subnet reachable through the node by some other means
ncclientcan’t detect on its own, or on a node not runningncclientat all.
Under each route is a “Used by” disclosure — expand it and check off which other nodes on the network should actually receive a route to it.
Picking a route (consumer side)
On any other node’s details panel — visible without opening Advanced, since this is something you’re likely to change often:
- Use Subnet Router — a dropdown listing every other node on the network that advertises at least one subnet. Choosing one routes this node’s traffic for all of that gateway’s advertised subnets through it; choosing None stops using a subnet router.
- Use Exit Node — the same idea for full-tunnel routing: a dropdown of every other node advertising an exit route.
This works for every platform, not just desktop/ncclient nodes — a mobile node can
pick a subnet router or exit node too, since consuming a route needs no host
automation, just the generated Nebula config.
What happens automatically (Linux nodes running ncclient)
For a gateway node whose ncclient has confirmed it’s Linux (shown by the absence
of the amber warning under Advanced → Subnet Router & Exit Node Config):
- Nebula Commander generates the correct
tun.unsafe_routesentry (with the requiredvia) in every consumer node’s config, and signs the gateway’s own certificate with the-subnetsclaim Nebula requires before it will let that node route the CIDR at all. ncclienton the gateway pollsGET /api/device/advertised-routesand, when its own advertised routes change, enables IP forwarding (net.ipv4.ip_forward, andnet.ipv6.conf.all.forwardingif any route is IPv6) and installs a dedicatedinet ncclient_routingnftables table: forwarding accept rules scoped to each advertised subnet, and a masquerade rule for the exit-node case.
None of this needs the admin to touch the host directly. nft (nftables) must be
installed on the gateway for step 2 to work - ncclient logs a warning and skips it
if nft isn’t found, leaving the route inert.
What you have to do yourself (hosts not running ncclient on Linux)
This includes Windows nodes, macOS, a Docker-deployed ncclient (it runs in its own
network namespace, so it can’t reach the host’s routing table at all), or any host
running the bare nebula binary directly. Nebula Commander still generates the
correct config for these nodes - ncclient’s automation is the only piece that’s
Linux-only. On such a gateway node you need to:
Get the config onto the host. Either let a non-Linux/bare
ncclientwrite it normally, or download it yourself from the node’s Config button (admin UI) orGET /api/device/config(device token) and place it wherenebula -config <path>expects it.Enable IP forwarding.
- Linux (no
ncclient):sysctl -w net.ipv4.ip_forward=1(andnet.ipv6.conf.all.forwarding=1for IPv6 routes/exit-node), persisted via a file under/etc/sysctl.d/. - Windows: enable IP forwarding on the network adapter bound to the Nebula tun
device, and configure routing/NAT (e.g. via
netsh interface ipv4 set interface "<adapter>" forwarding=enabled, plus RRAS if you need NAT for an exit node) - consult Microsoft’s routing documentation for your Windows version. - macOS:
sysctl -w net.inet.ip.forwarding=1, pluspfctlfor NAT.
- Linux (no
Allow forwarding and (for an exit node) NAT between the Nebula tun interface and your physical interface. On Linux with nftables, this is exactly what
client/linux_routing.pydoes forncclient- use it as a reference. For a subnet route to192.168.1.0/24via tun devicenebula1:table inet ncclient_routing { chain forward { type filter hook forward priority filter; policy accept; iifname "nebula1" ip daddr 192.168.1.0/24 accept ip saddr 192.168.1.0/24 oifname "nebula1" accept } }For an exit node, add a NAT table masquerading traffic from the tun device out your uplink interface (
eth0below):table inet ncclient_routing { chain postrouting { type nat hook postrouting priority srcnat; policy accept; iifname "nebula1" oifname "eth0" masquerade } }iptables-only systems need the equivalentiptables -A FORWARD .../iptables -t nat -A POSTROUTING ... -j MASQUERADErules.
Troubleshooting
nebula fails to start with Could not parse tun.unsafe_routes: entry N.via ... is not present - you’re running a config from before Nebula Commander added the
via/-subnets fix, or a hand-edited config that omits via. Re-download the
config; every generated tun.unsafe_routes entry always includes via now. If this
is your own config (not Nebula Commander’s), every entry needs a via pointing at
the gateway node’s Nebula IP.
A consumer node has the route in its config, but traffic to the subnet doesn’t
arrive - most likely the gateway’s own firewall. Since Nebula 1.10, a firewall rule
only matches traffic to the node’s own Nebula IP unless it also sets local_cidr -
Nebula Commander adds local_cidr-scoped copies of the gateway’s inbound rules
automatically for every subnet it advertises, but a manually-written config for a
non-ncclient host needs the same treatment by hand. See Nebula’s firewall
docs for the exact rule shape.
The route works for one node but not another - check that node’s selection: either its own Use Subnet Router/Use Exit Node dropdown, or the “Used by” list on the gateway it should be using. A route only reaches nodes explicitly selected on one side or the other; an unselected node’s config simply won’t contain the route at all.