Linux network bridges are the invisible plumbing under every container, every VM, and every modern lab network. When a bridge silently drops frames, when STP elections produce loops, when a member port flaps, the symptoms show up in application logs as inscrutable timeouts. This guide covers the bridge tooling that has replaced legacy brctl, the per-port counters that catch failures, and the monitoring routine that surfaces problems before they cascade.
The modern bridge command
brctl is deprecated; ip link and bridge are the supported replacements:
ip -d link show type bridge # show all bridges with options
bridge link show # member ports of all bridges
bridge link show master br0 # only members of br0
bridge -s link show # statistics per link
bridge fdb show # forwarding database (MAC table)
bridge mdb show # multicast forwarding database
The -d flag on ip link reveals bridge-specific parameters: VLAN filtering, STP state, ageing time. Read these before assuming a bridge is misconfigured.
Creating and tearing down a bridge
sudo ip link add name br0 type bridge
sudo ip link set br0 up
sudo ip link set eth1 master br0 # add eth1 as a port
sudo ip link set eth1 up
sudo ip addr add 10.0.0.1/24 dev br0
# remove
sudo ip link set eth1 nomaster
sudo ip link del br0
For persistence, use NetworkManager (nmcli con add type bridge ...) or netplan (Ubuntu) or systemd-networkd .netdev+.network files. Hand-built bridges via ip link survive only until reboot.
Forwarding database health
Each bridge maintains a MAC-to-port table. Stale or oversized FDBs indicate problems:
bridge fdb show br br0 | wc -l # current entry count
bridge fdb show br br0 | awk '$NF=="permanent"' | head
bridge fdb show br br0 | awk '$NF=="self"' | wc -l # local addresses
ip -d link show br0 | grep ageing_time # default 300 sec
An FDB with thousands of entries on a small lab bridge usually means a downstream switch is flooding into Linux. A cleared FDB right after a flap means the bridge re-learned everything โ keep an eye on this metric.
STP and loop prevention
Spanning Tree Protocol is off by default on Linux bridges. If two bridges connect to the same Layer 2 domain you risk loops. Enable STP on bridges that face untrusted networks:
sudo ip link set br0 type bridge stp_state 1
sudo ip link set br0 type bridge priority 32768
bridge link show | head # check state: forwarding/learning/blocking
ip -d link show br0 | grep stp_state
For container hosts on a single physical NIC, STP is typically unnecessary; for hosts that bridge multiple physical NICs, enable it.
VLAN filtering on a bridge
Modern Linux bridges support 802.1Q natively without a separate vlan interface:
sudo ip link set br0 type bridge vlan_filtering 1
sudo bridge vlan add vid 10 dev eth1
sudo bridge vlan add vid 20 dev eth1
sudo bridge vlan add vid 10 dev br0 self
bridge vlan show
Far more efficient than the old "create one bridge per VLAN" pattern. Validate with bridge vlan show after every change.
Per-port statistics
ip -s -s link show eth1 # rx/tx packets, errors, dropped
ethtool -S eth1 | grep -i error
cat /sys/class/net/br0/brif/eth1/state
cat /sys/class/net/br0/brif/eth1/path_cost
The bridge port state (0=disabled through 3=blocking; 3=forwarding is healthy in older kernels โ check your kernel docs) tells you whether STP has put the port into a non-forwarding state.
Monitoring script
#!/bin/bash
for br in $(ip -j link show type bridge | jq -r '.[].ifname'); do
echo "== $br =="
ip -s link show "$br" | grep -A1 'RX:\|TX:'
echo "Members:"
bridge link show master "$br" | awk '{printf " %-15s state=%s\n", $2, $7}'
fdb=$(bridge fdb show br "$br" | wc -l)
echo "FDB entries: $fdb"
if [ "$fdb" -gt 1000 ]; then echo "WARN: FDB unusually large"; fi
done
Run on a five-minute timer; ship to your log collector. Alert on member port state changes or sudden FDB growth.
Container-runtime bridges
Docker creates docker0 by default and one bridge per user-defined network. Inspect them like any other bridge:
docker network ls
ip link show type bridge
bridge link show
brctl show 2>/dev/null # legacy view, still works
Docker bridges enable IP masquerade via iptables; misconfigured firewall rules show up as "containers can talk to each other but not to the internet." Verify with iptables -t nat -L POSTROUTING -n -v | grep MASQUERADE.
Common pitfalls
- Adding a member port that already has an IP address โ the IP becomes inaccessible because the port is now in the bridge.
- Forgetting
net.ipv4.ip_forward=1when the bridge is the default gateway for downstream hosts. - Running two STP-enabled bridges with default priority โ the election picks an arbitrary root, which is rarely what you want.
- Bridging Wi-Fi interfaces โ most Wi-Fi drivers do not support promiscuous mode, breaking the bridge silently.
Bridges are a Layer 2 concern that hides under every Layer 3 problem you debug. Keep bridge link and ip -s link in muscle memory, run the monitoring script as a timer, and you will catch most bridge issues before they turn into 03:00 incidents.