Site-to-VPN: Clientless Devices Reaching NetBird Peers

This guide shows how to let a device that does not have NetBird installed initiate a connection to a NetBird peer over the overlay — the reverse of the more common VPN-to-Site direction.

What You'll Achieve

After following this guide, a clientless device on your local network can reach a NetBird peer by its overlay IP or NetBird DNS name. Typical examples:

  • An on-premise monitoring system pushing metrics to a NetBird-connected collector
  • A legacy server initiating outbound backups to a NetBird peer in the cloud
  • An office printer reporting status to a NetBird-connected management application
Clientless Device ──► Routing Peer ──► NetBird Overlay ──► NetBird Peer
   (no NetBird)         (peer)                              (peer)

Prerequisites

  • A device on the local network to serve as the routing peer. Linux is strongly recommended for the routing peer because it can install the required outbound SNAT automatically (see the warning above).
  • A separate device running the NetBird client that the clientless device needs to reach
  • The ability to either add a static route on the clientless device (or its upstream router), or to install a port-forwarding rule on the routing peer — both options are covered below

Example Setup

  • Local site: 192.168.50.0/24
  • Routing peer (site-router): site IP 192.168.50.10, NetBird IP assigned at enrollment
  • Clientless device: 192.168.50.20
  • Target NetBird peer (overlay-peer): runs the NetBird client; we reach it on TCP port 8080

Step 1: Create Setup Keys with Groups

Before installing NetBird on the routing peer, create a setup key with an auto-assigned group so the peer lands in the right place:

  1. Go to Setup Keys in the NetBird dashboard
  2. Click Create Setup Key
  3. Configure:
    • Name: "Site Routing Peer"
    • Auto-assigned groups: create and add site-routing-peers
  4. Click Create and note the key

The target peer is a regular NetBird peer; nothing special needs to be configured on it for this scenario. If the target peer is a service or appliance (not a user device), create a second setup key for it the same way and add it to a group like overlay-peers. User peers — laptops and workstations — enroll through SSO instead and pick up their group via your existing group assignments.

Step 2: Deploy the Routing Peer

Install NetBird on the routing peer and enroll it:

curl -fsSL https://pkgs.netbird.io/install.sh | sh
sudo netbird up --setup-key YOUR_SITE_SETUP_KEY

Confirm the peer appears in the dashboard and shows the site-routing-peers group.

Step 3: Configure the Outbound SNAT (If applicable)

The routing peer must SNAT site traffic onto its NetBird interface so the overlay peer's access control sees a NetBird IP it recognises. See Outbound SNAT requirement for the reasoning.

On Linux: no manual SNAT configuration is needed. NetBird enables IP forwarding and installs the SNAT itself when masquerade is enabled on the routing peer (Step 4).

The only Linux-side caveat is if you run a host firewall (UFW, firewalld) with the FORWARD chain default set to DROP — in that case, allow forwarding between the site-facing interface and wt0:

sudo iptables -I FORWARD 1 -i <site-iface> -o wt0 -j ACCEPT

On any other platform — pfSense, OPNsense, MikroTik, Windows, macOS, or Linux running in userspace mode — NetBird does not install the SNAT for you. Configure it manually on the routing peer or its upstream firewall.

For pfSense / OPNsense, add an outbound NAT rule on the wt0 (or equivalent) interface that translates traffic sourced from 192.168.50.0/24 to the interface address. Switch outbound NAT mode to Manual (or Hybrid) so this rule is honoured.

For MikroTik (RouterOS):

/ip firewall nat add chain=srcnat src-address=192.168.50.0/24 \
  out-interface=wt0 action=masquerade

Any outbound source NAT mechanism that rewrites the site-CIDR source to the routing peer's NetBird IP (or to the wt0 interface address) on the NetBird egress path is sufficient.

Step 4: Create the Network

In the NetBird dashboard:

  1. Go to Networks and click Add Network
  2. Name: site-50-network
  3. Click Create Network

Now add the site CIDR as a resource:

  1. In the new network, click Add Resource
  2. Configure:
    • Name: site-50
    • Address: 192.168.50.0/24
    • Type: Subnet
    • Groups: create and add site-50-cidr (this group represents the site CIDR for use in policies)
  3. Click Add Resource

Attach the routing peer:

  1. In the network, click Add Routing Peer
  2. Select site-router (or the site-routing-peers group)
  3. Masquerade: Enabled is fine, but does not replace Step 3 — this dashboard flag does not install a working SNAT on non-Linux routing peers and is unreliable in userspace mode
  4. Click Save

Step 5: Create the Access Policy

The routing peer needs to be allowed to reach the target peer over the overlay. Because Step 3's SNAT rewrites the source to the routing peer's NetBird IP, the policy uses peer groups:

  1. Go to Access ControlPolicies and click Add Policy
  2. Configure:
    • Name: Site Router to Overlay Peer
    • Protocol: TCP
    • Ports: 8080 (or All to allow any port)
    • Source Groups: site-routing-peers
    • Destination Groups: overlay-peers
  3. Click Add Policy

Step 6: Direct Site Traffic Through the Routing Peer

Tell the clientless device — or the site's upstream router — to send traffic destined for your account's NetBird IP range through the routing peer.

Find your account's NetBird range

NetBird assigns each account a single /16 block from inside the 100.64.0.0/10 CGNAT range (one of 64 possible blocks such as 100.64.0.0/16, 100.121.0.0/16, 100.127.0.0/16, …). The block is chosen randomly per account and can be customised. Use that /16 for the site's static route — not the whole /10 — so you don't route unrelated CGNAT addresses through the routing peer.

Read it off any enrolled peer:

$ netbird status | grep "NetBird IP"
NetBird IP: 100.121.195.4/16
# → this account's block is 100.121.0.0/16

In the examples below, replace 100.121.0.0/16 with your own block.

Install the route

On a Linux clientless device:

sudo ip route add 100.121.0.0/16 via 192.168.50.10
# Persist via /etc/network/interfaces, netplan, or NetworkManager

On Windows:

route -p add 100.121.0.0 mask 255.255.0.0 192.168.50.10

On a site router that issues DHCP: add a classless static route option (DHCP option 121) pointing your account's /16 to the routing peer. Every device on the network will then learn the route automatically.

The clientless device can now reach the target peer by its NetBird IP:

curl http://<TARGET_PEER_NETBIRD_IP>:8080/

To use NetBird's DNS names instead of IPs, see Resolving NetBird DNS Names below.

If you cannot add a static route on the clientless device or the site router — or you only need to expose a small number of specific services — see Appendix: Per-Service Port Forwarding for a DNAT-based alternative.

Test Connectivity

From the clientless device:

curl -v http://<TARGET_PEER_NETBIRD_IP>:8080/

Verify on the target peer that the request arrived:

sudo ss -tnp | grep :8080

The connection's remote address on the target peer will be the routing peer's NetBird IP, not the clientless device's local IP.

Resolving NetBird DNS Names

By default, the clientless device has no way to resolve *.netbird.cloud hostnames — that lookup happens locally on each NetBird peer. You can publish those names to the site by running a forwarding resolver on the routing peer.

A minimal dnsmasq configuration on the routing peer:

# /etc/dnsmasq.d/netbird.conf
bind-interfaces
listen-address=192.168.50.10
interface=eth0

# Forward NetBird-managed names to this peer's local NetBird resolver.
# The NetBird daemon listens on the peer's NetBird IP, port 53.
server=/netbird.cloud/<ROUTING_PEER_NETBIRD_IP>

# Everything else to upstream
server=8.8.8.8
server=1.1.1.1

Then point the clientless device's DNS at 192.168.50.10 (via DHCP, /etc/resolv.conf, or static configuration) and use the NetBird hostname directly:

curl http://overlay-peer.netbird.cloud:8080/

Outbound SNAT requirement

NetBird's per-peer access control on the destination peer matches inbound traffic against an ipset of allowed source IPs. The ipset is populated from the NetBird IPs of peers in the policy's source group — it cannot contain a routed CIDR like 192.168.50.0/24. So when a packet from 192.168.50.20 arrives at the overlay peer, the access control has no matching entry and the packet is dropped.

The fix is to rewrite the source IP at the routing peer before the packet enters the overlay, replacing the site IP with the routing peer's NetBird IP. That NetBird IP is in the policy's source group, so the access control matches and the packet is accepted.

On a Linux routing peer running in kernel mode, NetBird installs the SNAT itself via the kernel netfilter hooks when masquerade is enabled on the routing peer. On any other platform — pfSense, OPNsense, MikroTik, Windows, macOS, or Linux in userspace mode — that hook isn't available, so the SNAT must be configured manually on the routing peer or on its upstream firewall, as shown in Step 3.

Troubleshooting

Connection times out from the clientless device.

Check that the static route is in place:

ip route get <TARGET_PEER_NETBIRD_IP>
# Should show "via 192.168.50.10 dev <iface>"

If you are using the appendix DNAT alternative instead, verify the forwarding rule on the routing peer:

sudo iptables -t nat -L PREROUTING -n -v
# Should show your DNAT rule with non-zero packet counters when traffic flows

Routing peer receives packets but they don't reach the target peer.

Check that IP forwarding is enabled and confirm the target peer is reachable from the routing peer directly:

# On the routing peer
cat /proc/sys/net/ipv4/ip_forward    # should be 1
curl http://<TARGET_PEER_NETBIRD_IP>:8080/   # should succeed

If the second command fails, the access policy is wrong — verify the policy allows site-routing-peers → target peer's group on the required port.

Target peer receives packets but drops them.

The outbound SNAT in Step 3 is missing or not effective. On the routing peer, packets going out wt0 must have their source IP rewritten to the routing peer's NetBird IP:

# On the routing peer, verify packet counters on the MASQUERADE rule:
sudo iptables -t nat -L POSTROUTING -n -v
# Or watch overlay traffic on the way out:
sudo tcpdump -ni wt0 'src net 192.168.50.0/24'
# Seeing site IPs here means SNAT is NOT firing; the target peer will drop.

If the routing peer is Linux in kernel mode and masquerade is enabled but the SNAT counters stay at zero, drop in an explicit rule as a fallback:

sudo iptables -t nat -A POSTROUTING -s 192.168.50.0/24 -o wt0 -j MASQUERADE

DNS resolution returns NXDOMAIN.

Confirm the routing peer's NetBird IP is correct in dnsmasq.conf (it changes if the peer is re-enrolled), and that dnsmasq is not bound to lo — binding loopback causes it to refuse forwarding to its own 127.0.0.1-co-located NetBird resolver.

Appendix: Per-Service Port Forwarding

If you cannot change routing on the site — for example, the clientless device's IP stack is fixed and the upstream router is out of your control — you can still expose individual NetBird services through the routing peer using DNAT. This avoids the Step 6 static route entirely, but each service has to be configured explicitly.

First identify the routing peer's site-facing interface:

ip -br addr
# Pick the interface that holds the routing peer's site IP (e.g. eth0, ens18).

Then install the DNAT rule on the routing peer:

sudo iptables -t nat -A PREROUTING -i <site-iface> -p tcp --dport 18080 \
  -j DNAT --to-destination <TARGET_PEER_NETBIRD_IP>:8080
# Persist via iptables-persistent / netfilter-persistent

The clientless device now reaches the service through the routing peer's local IP and the forwarded port:

curl http://192.168.50.10:18080/

The outbound SNAT configured in Step 3 applies to this traffic as well — the target peer still observes the routing peer's NetBird IP as the source.

Each forwarded service needs its own DNAT rule. This pattern is a good fit for a small number of well-known services; for general overlay access, use the static-route approach in Step 6 instead.