OPNsense as the routing peer for a NetBird mesh

In short. Put the NetBird agent on both firewalls and register them with a setup key into a routing-peers group. In the dashboard, add one network route per VLAN, not a site /16, with that group as the routing peers. On the firewall, assign the overlay interface, add an outbound NAT rule for traffic leaving into the overlay, and write filter rules on the overlay interface as you would on any other. Never let a peer's tunnel endpoint sit inside a prefix it advertises.

NetBird peers can reach the firewall but nothing behind it

The agent is up, netbird status says connected, you can ping the firewall's overlay address, and every host on the VLAN behind it times out. Usually one of three things: the route was never added in the dashboard, the return path is missing because nothing translates traffic back into the overlay, or the firewall is dropping it on an interface with no rules yet. Occasionally it is a fourth thing, which gets its own gotcha.

What you need

Steps

  1. Install the agent on each firewall. With the plugin, install it from the Plugins tab and configure the management URL and setup key on its settings page. With the package:
pkg install netbird
sysrc netbird_enable=YES
service netbird start
netbird up --management-url https://nb.example.net --setup-key "$SETUP_KEY"

The setup key comes from the secret manager, puts the peer straight into the routing-peers group, and is expired once both nodes are registered.

  1. Name the peers. In the dashboard, rename them fw01-site1 and fw02-site1. Every policy, route and conversation from here on refers to peers by name.

  2. Assign the overlay interface on each firewall. The agent creates a WireGuard-style interface (wt0 on FreeBSD). Interfaces > Assignments shows it; add it, enable it with the description NETBIRD, no address configuration (the agent manages the address). Do this on both nodes; the sync does not assign interfaces.

  3. Add the network routes in the dashboard. Networks, add a network for the site, then add one resource per VLAN: 10.1.101.0/24 for tenant 1, 10.1.10.0/24 for OPS-INT, and so on. Routing peers: the group containing fw01-site1 and fw02-site1, so a failed node does not take the route with it. Access control: the groups that should reach each resource. One resource per VLAN rather than a site /16 means a policy can grant an operator the tenant VLAN and not the storage network, and the management VLAN is never included by accident.

  4. Add the outbound NAT for traffic leaving into the overlay. The agent's masquerade covers packets arriving from the overlay and going into the VLAN. It does nothing for connections that start on the VLAN side and go into the overlay, such as monitoring from OPS-INT probing a remote peer. Firewall > NAT > Outbound: set the mode to hybrid, then add a rule: interface NETBIRD, source 10.1.0.0/16 (the site's VLANs), destination 10.254.0.0/16, translation the interface address. Both nodes.

  5. Write filter rules on the overlay interface. Firewall > Rules > NETBIRD. Use the same block-first ordering as a tenant interface: block to MGMT, block to STORAGE, then pass from 10.254.0.0/16 to the VLAN resources you advertised. NetBird policies decide which peer may send packets to the routing peer; the packet filter decides what those packets may reach. The policy is transport; authorisation lives here and at the service.

  6. Check the endpoints. No peer may use a tunnel endpoint inside a prefix that is advertised through the overlay. The firewalls peer from their WAN address, outside everything they advertise, so they are fine. The case to watch is a VM inside a tenant VLAN running its own agent while the firewall advertises that VLAN: the VLAN route pulls the VM's own tunnel into the overlay, it is encapsulated twice, and large packets are dropped silently. Exclude that host from the route, or do not run an agent on it.

flowchart LR
  L["Operator laptop<br/>10.254.0.21"] -->|"WireGuard"| M["NetBird management<br/>and relay"]
  L -->|"direct or relayed"| A["fw01-site1<br/>wt0 10.254.0.2<br/>routing peer"]
  L -.-> B["fw02-site1<br/>wt0 10.254.0.3<br/>routing peer"]
  A -->|"route 10.1.101.0/24<br/>filter on NETBIRD"| T["Tenant 1 VLAN<br/>10.1.101.0/24"]
  A -->|"outbound NAT<br/>10.1.0.0/16 to overlay"| L

Verify it worked

On each firewall:

netbird status --detail
netbird networks list

On agents older than the 0.30 series the second command is netbird routes list; the output is the same idea. Status should show the management connection, the relay, and the laptop as a connected peer, ideally direct. The networks list should show each VLAN resource with this peer as a routing peer. ifconfig wt0 shows the overlay address.

From the laptop: netbird status should list fw01-site1, ping 10.1.101.10 should reach the tenant ingress, and ssh to a host in OPS-INT should work if the policy allows it. Then stop the agent on fw01 (service netbird stop) and ping again; after a few seconds it should recover through fw02. Start fw01 again before you forget.

Gotchas

FAQ

Should the laptop route all traffic through the firewall as an exit node? Only if you make the firewall an exit node deliberately, per region, opt-in. A routing peer for VLAN resources is not a default route, and most operators want only the VLANs.

Does CARP matter here? Not for the overlay. Each firewall is its own peer with its own overlay address and its own routes; NetBird picks a routing peer and fails over at that layer. CARP keeps .254 alive for the VLAN side. The two mechanisms do not need to know about each other.

Why the firewall and not a small VM as the routing peer? Because the firewall is already on every VLAN with the packet filter in the path, and it is already a pair. A VM would need an interface on each VLAN and its own rules, a third thing to keep in sync.

Related