Skip to content

🛠️Guide: Setting up a ZeroTier Site-to-Site Virtual Bridge

7 min read

I personally couldn't find much information about how to set up a site-to-site bridged network with ZeroTier. A guide exists for the very specific purpose that you want to do this with a Raspberry Pi, and want to replace its default Raspbian network stack with a full systemd-based one (why?), but I thought it would be helpful to explain it from a more general perspective.

WARNING

❗ This post details how to set up a Site-to-Site virtual ethernet bridge using ZeroTier. For a non-technical overview of why you might want to do this, see my previous post.

Is This Right For Me? ​

So you have two physical networks at different locations that you want to join. What are the benefits of joining them with ZeroTier L2 versus another solution like an L3 VPN (WireGuard)? I also list some alternatives below.

TypeAspectDetails
✅ ProNative MulticastMulticast traffic (mDNS, DLNA, SSDP) works between the two network segments as if they were one. WireGuard doesn't support this natively.
✅ ProNAT TraversalZeroTier uses zeroconf NAT hole-punching that just works behind NAT and firewalls — no need to mess with port forwarding.
✅ ProRelay FallbackIf direct NAT traversal fails, your network still operates using ZeroTier's (slower) relay servers.
✅ ProSimple AuthNo need to manually generate keys or distribute client configs like WireGuard — simply authorise devices in ZeroTier Central.
⚠️ ConBroadcast OverheadBridging at Layer 2 sends additional broadcast traffic over the tunnel, which uses a small amount of extra bandwidth.
⚠️ ConThird-Party CoordinationUnless self-hosting controllers/moons, you rely on ZeroTier's servers for peer discovery and network management.

Other Alternatives ​

OpenVPN TAP ​

I personally consider OpenVPN (somewhat) obsolete when WireGuard has a much smaller overhead and achieves greater speeds. However, its TAP mode operates at L2 and can be bridged. One issue with this is discoverability. If both networks are behind NAT and port forwarding isn't possible, there's no way for either to see the other. At least one host acting as the server must be discoverable.

VXLAN over L3 Tunnel (WireGuard) ​

The VXLAN tunnelling protocol encapsulates Layer 2 Ethernet frames in Layer 3 UDP packets, enabling the creation of virtualized Layer 2 subnets that span physical Layer 3 networks. The device that performs the encapsulation and decapsulation of packets is called a VXLAN tunnel endpoint (VTEP). It is possible to operate a VXLAN tunnel on top of a WireGuard tunnel. It has the same issue as above regarding discoverability.

Bill of Materials ​

  • At least two physical networks at different sites with independent internet connections. Each of these needs a gateway.
  • A device on each physical network capable of running the ZeroTier client, bridging interfaces, and IP forwarding.

Setup ​

  • Define your subnet $SUBNET and desired gateway device $GATEWAY. I use $SUBNET=10.10.0.0/22 and $GATEWAY=10.10.0.1, with the gateway being my router at home with its public IP and unrestricted internet connection.
  • Divide your $SUBNET into individual IP ranges for each network segment: $SUBNET_A, $SUBNET_B, ... and for the auto-assigned ZeroTier IPs $SUBNET_ZT. I used $SUBNET_A=10.10.0.0/24, $SUBNET_B=10.10.1.0/24, and $SUBNET_ZT=10.10.2.0/24.
  • Pick a device in each subnet to install ZeroTier on - let's call them $DEV_A and $DEV_B. I used the gateway router in my remote subnet and a Linux server in my home subnet. It's best to make sure these have static IPs to avoid any issues.

INFO

👀 It isn't strictly necessary to run a DHCP server for each segment of your network; a new device could simply wait for an advertisement from the main DHCP server. If you know what you're doing, I assume you could achieve a setup with multiple gateways, such as one linked into ZeroTier from a VPS if you didn't want to stretch your home ISP bandwidth too far. I leave this to your discretion.

  • Go to ZeroTier Central (ZTC) and create a network. Record its 16-character Network ID as $ZT_NET_ID. Assign the network $SUBNET to the managed routes section in ZeroTier Central.
  • Connect the devices by entering $ZT_NET_ID into the client devices' ZT interface. In ZTC, find the devices, give them a human-readable name, assign their static IPs, and check the Allow Bridging box in the device settings. You can now authorise them in ZTC.

INFO

Optional - Individual Remote Device Access

Suppose you're using your mobile or PC on a third party network and want to connect to your home network. With ZeroTier, it can be as simple as downloading the ZeroTier app, adding the network ID to it, and authorising the device in ZeroTier Central.

To enable this, we need to add a default route 0.0.0.0/0 via $GATEWAY to the managed routes of our ZT network, and configure the auto-assign range to some non-conflicting address range ($SUBNET_ZT).

  • For each ZT client device, there will now be an interface for the connection. On Linux, the ZT interface begins zt.... Disable allowDefault and allowManaged in the ZT software:
shell
sudo zerotier-cli set $ZT_NET_ID allowManaged=0
sudo zerotier-cli set $ZT_NET_ID allowDefault=0
  • Find the interface that connects to the rest of the network (e.g. eth0, wlan0, br0). If it's already a bridge (such as br-lan on OpenWrt), simply add $ZT_IF to it. If not, create a new bridge (br0), enslave both your physical interface and $ZT_IF, and move the host's static IP onto br0:
shell
# Create br0 and attach both the physical NIC and ZeroTier interface
sudo ip link add name br0 type bridge
sudo ip link set dev eth0 master br0
sudo ip link set dev $ZT_IF master br0

# Move the host's static IP from eth0 onto br0 and bring it up
sudo ip addr flush dev eth0
sudo ip addr add 10.10.0.2/22 dev br0
sudo ip link set dev br0 up
sudo ip route add default via 10.10.0.1 dev br0
ini
# /etc/systemd/network/10-br0.netdev
[NetDev]
Name=br0
Kind=bridge

# /etc/systemd/network/20-br0-members.network
[Match]
Name=eth0 zt*

[Network]
Bridge=br0

# /etc/systemd/network/30-br0.network
[Match]
Name=br0

[Network]
Address=10.10.0.2/22
Gateway=10.10.0.1
DNS=10.10.0.1
text
# Add the ZeroTier interface (zt...) to the existing br-lan device
config device
    option name 'br-lan'
    option type 'bridge'
    list ports 'eth1'
    list ports 'ztxxxxxxxx'

WARNING

⚠️ The Docker / br_netfilter Gotcha on Linux

If your Linux bridge host also runs Docker (or has the br_netfilter kernel module loaded), Linux passes Layer 2 bridged frames through iptables, where Docker's default FORWARD DROP policy will silently drop traffic between eth0 and $ZT_IF. Either disable netfilter on bridges via sysctl or explicitly allow forwarding across br0:

shell
sudo sysctl -w net.bridge.bridge-nf-call-iptables=0
sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0
shell
sudo iptables -I FORWARD -i br0 -o br0 -j ACCEPT

Verification ​

Once both bridge nodes are up, devices on $SUBNET_B should be able to reach $GATEWAY (10.10.0.1) at Layer 2 (arping 10.10.0.1), and multicast service discovery (mDNS / DLNA / Chromecast) will traverse the tunnel transparently as a single flat broadcast domain.