$linuxjunkies
>

Linux Bridges (br0 etc.) Explained

Learn how Linux bridges (br0) work, when to use them for VMs and containers, how to configure them with iproute2, and how to manage STP correctly.

IntermediateUbuntuDebianFedoraArch10 min readUpdated June 7, 2026

Before you start

  • Root or sudo access on the target system
  • iproute2 installed (present by default on all major distros)
  • A spare NIC or VM environment to test without losing remote access
  • Basic understanding of IP addressing and Layer 2 networking

A Linux bridge is a virtual Layer 2 switch inside the kernel. It lets multiple network interfaces — physical NICs, TAP devices, VLANs, or virtual interfaces — share the same broadcast domain. If you run KVM virtual machines, LXC containers, or need to bind two physical segments together without routing, you need a bridge. This guide covers how bridges work, how to create and persist them with modern tools, and the specific concerns around Spanning Tree Protocol and VM networking.

When You Actually Need a Bridge

A bridge is the right tool when:

  • Your KVM VMs need to appear on the LAN with their own IP addresses (full Layer 2 presence).
  • You run LXC/LXD containers that must reach the physical network directly, not through NAT.
  • You want to bind two physical NICs into a single L2 segment (rare on servers, common in embedded/appliance work).
  • You are building a software-defined network lab and need to patch virtual ports together.

If you only need containers to reach the internet and don't care about inbound LAN visibility, NAT (the default Docker/Podman mode) is simpler. Bridges add complexity; use them when you need them.

bridge-utils vs iproute2

bridge-utils provides the brctl command. It works, but it is a legacy package that receives no new features. The iproute2 suite — specifically the ip link and bridge commands — is the modern replacement and is installed by default on every major distro. Use iproute2 for new work. You may still encounter brctl in old scripts or documentation; the table below maps the equivalents.

Taskbrctl (legacy)iproute2 (modern)
Create bridgebrctl addbr br0ip link add br0 type bridge
Add interfacebrctl addif br0 eth0ip link set eth0 master br0
Show bridgesbrctl showbridge link show
Enable STPbrctl stp br0 onip link set br0 type bridge stp_state 1

Creating a Bridge Manually (Runtime)

These commands take effect immediately but do not survive a reboot. They are ideal for testing before you commit to a persistent config.

# Create the bridge interface
ip link add name br0 type bridge

# Bring it up
ip link set br0 up
# Enslave a physical NIC (it loses its own IP — move any IP to br0)
ip link set eth0 master br0
ip link set eth0 up
# Assign an address to the bridge itself
ip addr add 192.168.1.50/24 dev br0
ip route add default via 192.168.1.1
# Verify
bridge link show
ip addr show br0

Output from bridge link show will list enslaved ports with their state (e.g., forwarding, learning). The exact output varies by kernel version and whether STP is active.

Persistent Configuration

Persistent bridge config differs significantly between distros. Pick your method below.

Debian / Ubuntu — systemd-networkd

Prefer systemd-networkd over /etc/network/interfaces on Ubuntu 20.04+ and Debian 11+. If you use NetworkManager (desktop installs), see the NetworkManager section instead.

# Create a netdev file for the bridge
cat > /etc/systemd/network/10-br0.netdev << 'EOF'
[NetDev]
Name=br0
Kind=bridge

[Bridge]
STP=yes
EOF
# Bind the physical NIC to the bridge
cat > /etc/systemd/network/20-eth0.network << 'EOF'
[Match]
Name=eth0

[Network]
Bridge=br0
EOF
# Configure the bridge's own IP (or use DHCP=yes for dynamic)
cat > /etc/systemd/network/30-br0.network << 'EOF'
[Match]
Name=br0

[Network]
Address=192.168.1.50/24
Gateway=192.168.1.1
DNS=192.168.1.1
EOF
systemctl enable --now systemd-networkd
systemctl restart systemd-networkd

Fedora / RHEL / Rocky — NetworkManager (nmcli)

NetworkManager is the standard on these distros since RHEL 8. Do not edit ifcfg files directly on RHEL 9+; use nmcli.

# Create the bridge connection
nmcli connection add type bridge ifname br0 con-name br0 \
  bridge.stp yes \
  ipv4.method manual \
  ipv4.addresses 192.168.1.50/24 \
  ipv4.gateway 192.168.1.1 \
  ipv4.dns 192.168.1.1
# Attach the physical interface as a bridge slave
nmcli connection add type ethernet ifname eth0 con-name br0-slave \
  master br0
# Bring up the bridge (the slave comes up automatically)
nmcli connection up br0

Arch Linux — systemd-networkd

Arch ships without a default network manager. systemd-networkd is the clean minimal choice. The file structure is identical to the Debian/Ubuntu example above; just ensure the service is enabled.

systemctl enable --now systemd-networkd systemd-resolved

Spanning Tree Protocol (STP)

STP prevents Layer 2 loops. When a bridge port initialises, it goes through blocking → listening → learning → forwarding states over roughly 30 seconds. On a simple VM bridge with no loops possible, STP delay is only a nuisance — DHCP requests time out before the port reaches forwarding. In that case, disable STP or enable Portfast/edge mode on the relevant port.

# Disable STP entirely on a bridge
ip link set br0 type bridge stp_state 0
# Or set a port to edge mode (no STP delay, still participates in STP)
bridge link set dev tap0 learning on flood on
bridge link set dev tap0 state 3   # 3 = forwarding immediately

If you connect physical switches to the bridge and loops are physically possible, leave STP on and set the bridge priority appropriately. The kernel bridge implements classic 802.1D STP, not RSTP. For RSTP you need Open vSwitch.

# Set bridge priority (lower = more preferred root; default 32768)
ip link set br0 type bridge priority 4096

VM and Container Bridges

KVM / libvirt

libvirt can manage its own NAT bridge (virbr0) automatically, but for VMs that need a real LAN presence, point libvirt at your manually-created br0.

# Define a libvirt bridge network pointing at br0
cat > /tmp/br0-net.xml << 'EOF'

  host-bridge
  
  

EOF

virsh net-define /tmp/br0-net.xml
virsh net-start host-bridge
virsh net-autostart host-bridge

Then set your VM's NIC source to network=host-bridge in virt-manager or the domain XML. The hypervisor creates a TAP device and enslaves it to br0 automatically each time the VM starts.

LXC / LXD

LXD creates its own bridge (lxdbr0) with NAT by default. To use your existing br0 instead:

# Change the default profile's NIC to use br0
lxc profile device set default eth0 nictype bridged
lxc profile device set default eth0 parent br0

A Note on Docker

Docker creates docker0 as a bridge automatically, but it is NAT-masqueraded. To put containers directly on your LAN, use the macvlan or ipvlan driver instead of attaching containers to br0 directly — the Linux bridge and macvlan do not play well on the same physical parent NIC without workarounds.

Verification

# See all bridges and their enslaved ports
bridge link show

# Full interface state
ip link show master br0

# Forwarding database (MAC address table)
bridge fdb show dev br0

# STP state per port
bridge link show

A healthy enslaved port shows state forwarding. If it shows state blocking or state listening indefinitely, check whether STP is causing an unexpected topology change or whether the port is administratively down.

Troubleshooting

  • VM gets no DHCP: The bridge port is still in STP learning state. Disable STP on the bridge or set the TAP port to forwarding immediately as shown above.
  • Host loses network when enslaving NIC: Expected — enslaving a physical NIC removes its IP. Assign the IP to br0 before or immediately after enslaving, or do both atomically via a config file and reboot.
  • Bridge shows UP but no traffic: Check ip6tables and nftables rules. The kernel parameter net.bridge.bridge-nf-call-iptables controls whether bridged traffic hits netfilter. On some distros this is 1 by default, so firewall rules can silently block bridge traffic.
  • br_netfilter dropping packets: Run sysctl net.bridge.bridge-nf-call-iptables. If it returns 1 and you are not intentionally filtering bridged traffic, set it to 0 in /etc/sysctl.d/.
  • NetworkManager keeps managing br0 slave: On Fedora/RHEL, unmanage the physical NIC before bridging: nmcli device set eth0 managed no, or let nmcli own it entirely as shown in the persistent config section.
tested on:Ubuntu 24.04Debian 12Fedora 40Arch rolling

Frequently asked questions

Can I bridge a Wi-Fi interface (wlan0)?
Usually not in infrastructure (managed) mode. Most Wi-Fi drivers do not support being enslaved to a software bridge because 802.11 requires all frames to carry the AP's MAC address. Use a wired NIC for the bridge uplink, or switch the Wi-Fi card to AP mode and use hostapd.
What is the difference between a bridge and a bond?
A bond (link aggregation) combines multiple NICs into one logical link for redundancy or throughput; it operates at Layer 2 but does not connect separate network segments. A bridge connects multiple segments into one broadcast domain. You can bond two NICs and then bridge the bond.
Does creating a bridge affect firewall rules?
Yes. If the br_netfilter kernel module is loaded and net.bridge.bridge-nf-call-iptables (or nftables equivalents) is 1, bridged traffic passes through the netfilter hook. Existing firewall rules may unexpectedly block VM traffic. Set the sysctl to 0 if you do not need to filter bridged frames.
Why does my VM lose network connectivity for 30 seconds after starting?
STP is making the newly created TAP port go through its state machine before reaching forwarding. Set the TAP port to forwarding immediately after creation, or disable STP on the bridge entirely if there are no physical loops.
Can I use a bridge with VLANs?
Yes. The kernel bridge supports VLAN filtering (ip link set br0 type bridge vlan_filtering 1). You can then assign VLAN IDs per port with the bridge vlan add command, giving you a software-managed 802.1Q switch without Open vSwitch.

Related guides