Using unsafe routes for access beyond overlay hosts
Hosts on a Nebula network may need to reach devices that cannot run Nebula, such as printers, physical access control systems, or proprietary devices. If such devices are on a subnet reachable by one of the Nebula hosts, that host can act as a subnet gateway, forwarding traffic from other Nebula hosts to the subnet devices.
This guide explains how to configure Nebula’s unsafe_routes feature to route traffic through the subnet gateway to the
devices that cannot run Nebula.
NEBULA OVERLAY NETWORK ░ SUBNET (HOME LAN) ░ ┌──────────────┐ ░ │ Roaming Host │────┐ ░ └──────────────┘ │ ░ │ ░ ┌────────────┐ │ ░ │ Lighthouse │────┤ ░ └────────────┘ │ ░ │ ░ (nebula1) │ ░ ┌───┴────────────┐ │ Subnet Gateway │ └───────────┬────┘ ░ │ (eth0) ░ │ ░ │ ┌─────────┐ ░ └────│ Printer │ ░ └─────────┘Prerequisites
Section titled “Prerequisites”Read the Quick Start guide to learn how to create a basic Nebula overlay network.
To complete this guide, you will need the following:
- A working basic Nebula overlay network with a lighthouse and at least two hosts:
- A roaming host running Linux, macOS, or Windows that needs access to the subnet devices
- The subnet gateway running Linux, macOS, or Windows that will forward traffic from the overlay network to the
subnet using
unsafe_routes
(This guide only covers configuring a Linux host as the subnet gateway)
- CA with the
nebula-certbinary able to sign host certificates with additional metadata required forunsafe_routes - The
ca.keyandca.crtfiles for the Nebula overlay network- In
ca.crt,unsafeNetworksmust be either not specified or list the required unsafe routes
- In
- Root access to the roaming host
- Root access to the subnet gateway
This guide assumes that you have the directory for the nebula and nebula-cert binaries in your $PATH.
CA cert
Section titled “CA cert”Check your ca.crt to ensure its unsafeNetworks (formerly -subnets) is either not specified or lists the subnets
you want to access. In the directory containing your CA cert, run:
nebula-cert print -path ca.crtIf unsafeNetworks is set and does not include the required subnets, you will have to regenerate all certificates from
scratch.
Here's an example of a CA generated without specifying unsafeNetworks:
# For more focused output, the -json flag is added and the result is piped to jqnebula-cert print -json -path ca.crt | jq .details{ "groups": null, "isCa": true, "issuer": "", "name": "BK Labs", "networks": null, "notAfter": "2027-09-23T10:24:58+08:00", "notBefore": "2026-09-23T10:24:58+08:00", "unsafeNetworks": null}Example network
Section titled “Example network”To illustrate how unsafe_routes works in practice, this guide uses an example scenario with the hostnames, IP
addresses, and subnets provided below.
Nebula overlay network (Internet)
Section titled “Nebula overlay network (Internet)”This is the overlay network with hosts running Nebula. Let's choose a common overlay network subnet 10.0.0.0/24 with
the IP range 10.0.0.1 – 10.0.0.254.
The nebula-roaming host in this example has Internet access but is not on the home LAN.
| Overlay host IP | Overlay hostname | Description |
|---|---|---|
10.0.0.1 |
lighthouse |
Nebula lighthouse |
10.0.0.5 |
subnet-gateway |
Overlay host routing traffic between overlay and subnet |
10.0.0.11 |
nebula-roaming |
Roaming host that needs access to the printer via subnet-gateway |
Subnet (Home LAN)
Section titled “Subnet (Home LAN)”This is the subnet on which a roaming host needs to access devices. Let's choose a common LAN subnet 192.168.1.0/24
with the IP range 192.168.1.1 – 192.168.1.254.
| Subnet device IP | Overlay hostname | Description |
|---|---|---|
192.168.1.5 |
subnet-gateway |
Overlay host routing traffic to subnet devices |
192.168.1.25 |
(none) | Printer that cannot run Nebula |
How to add unsafe routes
Section titled “How to add unsafe routes”With your Nebula overlay network and subnet up and running, add unsafe routes following the steps below.
On the subnet gateway:
- Step 1. Subnet gateway: Add unsafeNetworks to the certificate
- Step 2. Subnet gateway: Configure Nebula firewall
- Step 3. Subnet gateway: Configure traffic routing
On a client host:
Using the setup in Example network, this guide will show you how to configure the roaming host
(nebula-roaming, 10.0.0.11) to route traffic through the subnet gateway (subnet-gateway, 10.0.0.5 and
192.168.1.5) to reach the printer on the home LAN from anywhere.
Step 1. Subnet gateway: Add unsafeNetworks to the certificate
Section titled “Step 1. Subnet gateway: Add unsafeNetworks to the certificate”On your CA host, go to the directory containing your ca.crt and ca.key, then sign a new cert for the subnet gateway
with subnets you want to route:
nebula-cert sign -name 'subnet-gateway' -networks '10.0.0.5/24' -unsafe-networks '192.168.1.0/24'Inspect the new certificate to confirm that unsafeNetworks is now correctly set to the home LAN subnet.
nebula-cert print -json -path subnet-gateway.crt | jq .details{ "groups": null, "isCa": false, "issuer": "57903a07e52a8f5464636aeccb1942560324dcd1f6c4f0457d77b00372b5d9f2", "name": "subnet-gateway", "networks": [ "10.0.0.5/24" ], "notAfter": "2027-09-23T10:24:57+08:00", "notBefore": "2026-09-29T11:24:07+08:00", "unsafeNetworks": [ "192.168.1.0/24" ]}Next, copy the new host cert and key over to the subnet gateway:
# Replace user with your ssh usernamescp subnet-gateway.crt subnet-gateway.key user@10.0.0.5:~/After copying these files, login to your subnet gateway host and replace your existing cert and key files (commonly
stored in /etc/nebula) with the updated files. It's also a good practice to make sure that the key file is only
readable by root. If you change existing file paths for the cert and key, don't forget to update them in your Nebula
config.yml file.
Step 2. Subnet gateway: Configure Nebula firewall
Section titled “Step 2. Subnet gateway: Configure Nebula firewall”Besides adding the subnet to the cert's unsafeNetworks field, you need to add separate firewall rules on your subnet
gateway to regulate inbound traffic to your subnet from other overlay hosts. For this, add an inbound firewall rule with
local_cidr for subnet devices and ports that you need to reach.
To access the printer on the home LAN, you need to add an inbound firewall rule to allow TCP port 631 on
192.168.1.0/24 (depending on your printer, you may need to include additional ports). On the subnet gateway, edit the
Nebula configuration file (commonly located at /etc/nebula/config.yml) and add the following to existing firewall
rules:
firewall: inbound: - port: 631 host: any proto: tcp local_cidr: 192.168.1.25/32However, the easiest way to configure the firewall is to allow any port or protocol. In this case, on the subnet gateway, add the following to existing firewall rules:
firewall: inbound: - port: any host: any proto: any local_cidr: 192.168.1.0/24At this point we're finished configuring the Nebula-specific components on the subnet gateway. If Nebula is running, it's a good idea to restart it to validate that the configuration file, cert, and key are set up correctly.
$ sudo nebula -config /etc/nebula/config.yml# To improve readability, each output line has been redacted as follows:# time=2026-09-29T04:04:06.476Z level=INFO msg="..." redacted to msg="..."msg="Firewall rule added" firewallRule="map[caName: caSha: cidr: direction:outgoing endPort:0 groups:[] host:any localCidr: proto:0 startPort:0]"msg="Firewall rule added" firewallRule="map[caName: caSha: cidr: direction:incoming endPort:0 groups:[] host:any localCidr: proto:0 startPort:0]"msg="Firewall rule added" firewallRule="map[caName: caSha: cidr: direction:incoming endPort:0 groups:[] host:any localCidr:192.168.1.0/24 proto:0 startPort:0]"msg="Firewall started" firewallHashes=SHA:bec58c30ae0bd0463ff56a5fe9a75a21dc0253a44e3dcbe00e0d70dfb2fbe782,FNV:1666660931msg=listening addr=[::]:4242msg="Main HostMap created" preferredRanges=[]msg="punchy enabled"msg="Loaded send_recv_error config" sendRecvError=alwaysmsg="Loaded accept_recv_error config" acceptRecvError=alwaysmsg="Nebula interface is active" interface=nebula1 networks=[10.0.0.5/24] build=1.11.2 udpAddr=[::]:4242 boringcrypto=falsemsg="Handshake message sent" vpnAddrs=[10.0.0.1] localIndex=4215839296 remoteIndex=0 udpAddrs=[<lighthouse-public-ip>:4242] initiatorIndex=4215839296 handshake="map[stage:1 style:ix_psk0]"msg="Handshake message received" vpnAddrs=[10.0.0.1] from=<lighthouse-public-ip>:4242 certName=lighthouse certVersion=2 fingerprint=<...> issuer=<...> initiatorIndex=4215839296 responderIndex=1986196114 handshake="map[stage:2 style:ix_psk0]"If you see a Handshake message sent to your lighthouse followed by a corresponding received message after a similar
set of lines shown above you are good to go.
You can either leave Nebula running in the background or stop it while completing the next step on the subnet gateway.
Step 3. Subnet gateway: Configure traffic routing
Section titled “Step 3. Subnet gateway: Configure traffic routing”Now, you need to configure traffic routing on Linux. First, allow packet forwarding between network interfaces. Then, configure the firewall to do packet routing and NAT.
Enable IP forwarding
Section titled “Enable IP forwarding”Typically, packet forwarding is not enabled by default, and the respective kernel parameter is set to 0:
$ sudo sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 0You can update this variable at runtime as follows:
$ sudo sysctl -w net.ipv4.ip_forward=1net.ipv4.ip_forward = 1This change will be lost upon reboot.
To make it persist across reboots, run this to create a drop-in config file under /etc/sysctl.d/:
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-nebula.confConfigure packet routing
Section titled “Configure packet routing”Now that packet forwarding is enabled, you need to add a few firewall rules. First, the subnet gateway needs to route packets between Nebula and local network interfaces. Second, it needs to do network address translation (NAT).
Below you can find generic steps to configure the following:
Please note that the exact steps will depend on your firewall state.
For example, if your subnet gateway host runs Docker, its forward and postrouting rules may affect the routing rules proposed below.
These steps assume ufw is already active (sudo ufw status shows Status: active). If it isn't, allow SSH and Nebula's
UDP port first (for example, sudo ufw allow OpenSSH and sudo ufw allow 4242/udp), then run sudo ufw enable.
Enabling ufw without these rules can lock you out of the host.
First, allow packet routing between the Nebula interface and the local network interface:
(Replace network
interfaces (nebula1, eth0) and subnets with your actual ones)
sudo ufw route allow in on nebula1 out on eth0 from 10.0.0.0/24 to 192.168.1.0/24sudo ufw reloadConfirm the new rule is active:
$ sudo ufw statusStatus: active
To Action From-- ------ ----192.168.1.0/24 on eth0 ALLOW FWD 10.0.0.0/24 on nebula1Next, enable NAT to masquerade outgoing packets.
Open /etc/ufw/before.rules and add the NAT configuration block at the top of the file (before any *filter block):
# NAT table rules*nat:POSTROUTING ACCEPT [0:0]
# flush all existing POSTROUTING rules in the nat table to avoid duplicates on reload-F POSTROUTING
# 10.0.0.0/24 is the Nebula network, 192.168.1.0/24 is the route CIDR-A POSTROUTING -s 10.0.0.0/24 -d 192.168.1.0/24 -j MASQUERADE
# don't delete the 'COMMIT' line or these nat table rules won't be processedCOMMITReload UFW to apply the NAT configuration:
sudo ufw reloadnftables
Section titled “nftables”Make sure the nftables package is installed (sudo apt install nftables on Debian and Ubuntu) and the
/etc/nftables/ directory exists (sudo mkdir -p /etc/nftables).
Create a new nftables configuration (e.g., /etc/nftables/nebula.conf). Configure the packet forwarding chain and then
the NAT postrouting chain:
(Replace network interfaces (nebula1, eth0) and subnets with your actual ones)
table ip nebula_routingdelete table ip nebula_routingtable ip nebula_routing { chain forward { type filter hook forward priority filter; policy accept; iifname nebula1 oifname eth0 ip saddr 10.0.0.0/24 ip daddr 192.168.1.0/24 counter accept iifname eth0 oifname nebula1 ip saddr 192.168.1.0/24 ip daddr 10.0.0.0/24 ct state established,related counter accept }
chain postrouting { type nat hook postrouting priority srcnat; policy accept; ip saddr 10.0.0.0/24 ip daddr 192.168.1.0/24 counter masquerade }}To load the rules, run:
sudo nft -f /etc/nftables/nebula.confTo verify that the rules have taken effect, you can run:
$ sudo nft list table nebula_routingtable ip nebula_routing { chain forward { type filter hook forward priority filter; policy accept; iifname "nebula1" oifname "eth0" ip saddr 10.0.0.0/24 ip daddr 192.168.1.0/24 counter packets 6 bytes 504 accept iifname "eth0" oifname "nebula1" ip saddr 192.168.1.0/24 ip daddr 10.0.0.0/24 ct state established,related counter packets 6 bytes 504 accept }
chain postrouting { type nat hook postrouting priority srcnat; policy accept; ip saddr 10.0.0.0/24 ip daddr 192.168.1.0/24 counter packets 2 bytes 168 masquerade }}These changes will be lost upon reboot.
To make them persist across reboots, include this file in your main /etc/nftables.conf by adding the following line to
it:
include "/etc/nftables/nebula.conf"Also, make sure to enable the nftables service so the configuration is loaded at boot:
sudo systemctl enable nftablesiptables
Section titled “iptables”First, allow packet routing between the Nebula interface and the local network interface:
(Replace network
interfaces (nebula1, eth0) and subnets with your actual ones)
sudo iptables -I FORWARD 1 -i nebula1 -o eth0 -s 10.0.0.0/24 -d 192.168.1.0/24 -j ACCEPTsudo iptables -I FORWARD 2 -i eth0 -o nebula1 -s 192.168.1.0/24 -d 10.0.0.0/24 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPTNext, add the NAT rule to masquerade outgoing traffic:
sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -d 192.168.1.0/24 -j MASQUERADEOnce complete, confirm the forwarding rules are active:
$ sudo iptables -L FORWARD -v -nChain FORWARD (policy DROP 135 packets, 10404 bytes) pkts bytes target prot opt in out source destination 524 76K ACCEPT 0 -- nebula1 eth0 10.0.0.0/24 192.168.1.0/24 1034 1469K ACCEPT 0 -- eth0 nebula1 192.168.1.0/24 10.0.0.0/24 ctstate RELATED,ESTABLISHEDThen verify the NAT rule:
$ sudo iptables -t nat -L POSTROUTING -v -nChain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 MASQUERADE 0 -- * * 10.0.0.0/24 192.168.1.0/24You may see additional rules listed depending on your host and if other services (such as Docker) have modified iptables.
These changes will be lost upon reboot.
To make them persist across reboots, you need to run additional commands (e.g. iptables-save) depending on your Linux
distribution.
Step 4. Roaming host: Configure traffic routing
Section titled “Step 4. Roaming host: Configure traffic routing”The subnet gateway host is set up. We are almost there!
The final step is to configure the overlay network hosts to use unsafe_routes to route the traffic destined for the
subnet devices through the subnet gateway.
On nebula-roaming, make sure you have the following in the tun config section:
tun: drop_local_broadcast: true drop_multicast: true unsafe_routes: - route: 192.168.1.0/24 via: 10.0.0.5Run Nebula with the new configuration:
$ sudo nebula -config /etc/nebula/config.yml# To improve readability, each output line has been redacted as follows:# time=2026-09-29T04:04:06.476Z level=INFO msg="..." redacted to msg="..."msg="Firewall rule added" firewallRule="map[caName: caSha: cidr: direction:outgoing endPort:0 groups:[] host:any localCidr: proto:0 startPort:0]"msg="Firewall rule added" firewallRule="map[caName: caSha: cidr: direction:incoming endPort:0 groups:[] host:any localCidr: proto:0 startPort:0]"msg="Firewall started" firewallHashes=SHA:498215dec4e5687a2353f51c10838c113bd1af35ef72b8e8c9f536986ada5417,FNV:2782948616msg=listening addr=[::]:4242msg="Main HostMap created" preferredRanges=[]msg="punchy enabled"msg="Loaded send_recv_error config" sendRecvError=alwaysmsg="Loaded accept_recv_error config" acceptRecvError=alwaysmsg="Nebula interface is active" interface=nebula1 networks=[10.0.0.11/24] build=1.11.2 udpAddr=[::]:4242 boringcrypto=falsemsg="Added route" route=192.168.1.0/24msg="Handshake message sent" vpnAddrs=[10.0.0.1] localIndex=2523598458 remoteIndex=0 udpAddrs=[<lighthouse-public-ip>:4242] initiatorIndex=2523598458 handshake="map[stage:1 style:ix_psk0]"msg="Handshake message received" vpnAddrs=[10.0.0.1] from=<lighthouse-public-ip>:4242 certName=lighthouse certVersion=2 fingerprint=<...> issuer=<...> initiatorIndex=2523598458 responderIndex=1344867621 handshake="map[stage:2 style:ix_psk0]" durationNs=59967190 sentCachedPackets=1As you can see in msg="Added route", the unsafe route is being used.
Now you should be able to successfully ping any host on the subnet from your roaming host and access subnet devices:
- Ping working? Great! Go ahead and add that printer, map file shares, and SSH to any host on the home LAN.
- Ping not working? If both your roaming host and subnet gateway are behind NAT, they might have issues pinging each other. In this case, try using punchy or relay. For details, see NAT traversal and firewalls.
If the subnet gateway uses ufw, ping alone doesn't confirm that forwarding works: ufw's default rules forward ICMP echo
even without a ufw route allow rule. To check the routing rules, also connect to a TCP port on a subnet device, for
example the printer's IPP port with nc -zv 192.168.1.25 631.
Add the same unsafe_routes section to other overlay hosts that need access to subnet devices.
Avoid IP conflicts with underlay networks
Section titled “Avoid IP conflicts with underlay networks”When you are running Nebula with unsafe_routes the host's OS will forward all traffic destined for the unsafe route to
Nebula. This might cause a local IP conflict if your underlay network happens to use the same IP range as the subnet
specified in your unsafe_routes configuration.
Use unsafe routes with Mobile Nebula
Section titled “Use unsafe routes with Mobile Nebula”After you've successfully completed the steps above, you can also access your unsafe routes from a device running Mobile Nebula on Android or iOS. For this, launch the app and configure it as follows:
- Tap the site you want to configure
- Tap Configuration → Advanced → Unsafe routes
- Tap Add a new route and enter the same
routeandviavalues that you used in Step 4 - Tap Save
If connected, disconnect from the site. Now, connect again and try to access one of the hosts only accessible using
unsafe_routes.