| |

LFCE 20 🐧 Creating and Managing Bridges

⏱️ Estimated read: 13 min | |

The previous chapter established what a bridge is: a software switch that forwards frames between ports based on a learned MAC address table. This chapter is the operational sequence. Creating a bridge is a single command; making it work correctly requires a specific order of operations, and making it persist requires the correct configuration for the distribution’s network manager. The most common failure is not a missing command but a misordered one—attaching a physical interface to a bridge before moving its IP address to the bridge, which severs connectivity and, on a remote server, locks the administrator out of the very system they are configuring .

Key point: The IP address belongs on the bridge, not on the physical port. When a physical interface becomes a bridge port, it loses its routing identity. The address must be moved to br0 before or immediately after the port is attached, and on a remote system the bridge must have a working default route before the physical interface’s address is removed .


Why bridge management requires a careful sequence

The remote lockout problem.

On a local system, misconfiguring a bridge is an inconvenience. On a remote server reached over SSH, it is a lockout. The Arch Wiki explicitly warns that if you add the main network interface to a bridge, you must first assign the interface’s IP address to the bridge, bring the bridge up, and set up a backup default route—all before attaching the interface . If you attach the interface first and then try to configure the bridge, the SSH session dies mid-configuration. The sequence exists because the kernel does not allow two default routes on the same routing table; the workaround is to append the new route to the bridge before removing the old one from the physical interface .

The legacy-versus-modern tool problem.

Bridge management has two toolchains. brctl from the bridge-utils package is the traditional tool. ip link and bridge from iproute2 are the modern replacements. brctl is deprecated upstream; ip link and bridge are the current standard, and VLAN filtering features are only available through the bridge command . The commands in this chapter use iproute2 exclusively. The legacy equivalents exist for reference, but new configurations should not use them.

The persistence problem.

A bridge created with ip link add exists only in the running kernel. A reboot removes it. Persistence requires the distribution’s network manager—Netplan on Ubuntu, nmcli on RHEL, systemd-networkd on Arch and others, or /etc/network/interfaces on Debian. The running configuration and the persistent configuration must produce the same result, and the persistent configuration must be verified by rebooting, not by inspecting the file.


a. Creating the bridge and attaching ports

The bridge is created with ip link add, brought up with ip link set, and given an IP address with ip addr add. The physical interface is flushed of its address, attached as a port, and brought up.

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

# Bring the bridge up
ip link set br0 up

# Flush any IP address from the physical interface
# This must happen before or immediately after attaching it to the bridge
ip addr flush dev eth0

# Attach the physical interface to the bridge
ip link set eth0 master br0

# Bring the physical interface up
ip link set eth0 up

# Assign the host's IP address to the bridge
ip addr add 192.168.1.100/24 dev br0

# Add the default route through the bridge
ip route add default via 192.168.1.1 dev br0

The ip link show master br0 command verifies which interfaces are attached. The bridge link show command shows the state of each port, including whether STP is blocking it .

# Verify the bridge ports
ip link show master br0

# Show bridge port states
bridge link show

# Show the bridge interface details
ip -d link show br0

On a remote system, the sequence is stricter. The IP must be on the bridge and the default route appended before the physical interface’s address is deleted. The Arch Wiki recommends putting the attach-and-flush commands in a script and executing them in quick succession .

# Remote-safe sequence
ip link add name br0 type bridge
ip link set dev br0 up
ip address add 10.2.3.4/8 dev br0
ip route append default via 10.0.0.1 dev br0
# Then, in quick succession:
ip link set eth0 master br0
ip address del 10.2.3.4/8 dev eth0

The ip route append is necessary because the kernel does not allow two default routes in the same table. Once the address on eth0 is removed, the default route attached to it disappears, leaving the bridge’s route as the active one .


b. Managing the forwarding database and STP

The bridge learns MAC addresses dynamically, but the forwarding database (FDB) can also be inspected and manipulated manually. The bridge fdb command displays, adds, and deletes entries .

# Show the forwarding database for the bridge
bridge fdb show br br0

# The output shows MAC addresses, the port they are reachable through,
# and flags indicating whether the entry is local, static, or dynamic

Spanning Tree Protocol is disabled by default on many distributions. If the bridge connects to a topology where loops are possible, STP must be enabled. If the bridge is a simple leaf connection with no redundant paths, STP adds unnecessary delay and can be disabled .

# Check the current STP state
ip -d link show br0 | grep stp_state

# Enable STP
ip link set br0 type bridge stp_state 1

# Disable STP for a loop-free topology
ip link set br0 type bridge stp_state 0

The bridge link show output includes the port state. A port in forwarding state passes traffic. A port in blocking or listening state does not, which is the expected behavior when STP has detected a loop . If a port is unexpectedly blocked and no loop exists, STP may be misconfigured or the topology may have changed in a way that requires the forwarding delay to expire.


c. Persisting the bridge configuration

The ip commands modify the running kernel. Persistence depends on the network manager. The three most common mechanisms are Netplan, nmcli, and systemd-networkd.

# Netplan (Ubuntu): /etc/netplan/01-bridge.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: false
  bridges:
    br0:
      interfaces: [eth0]
      addresses: [192.168.1.100/24]
      routes:
        - to: default
          via: 192.168.1.1
      parameters:
        stp: false

Apply with netplan apply. The interfaces list specifies which physical interfaces are attached to the bridge. The parameters section controls STP .

# nmcli (RHEL/NetworkManager)
nmcli connection add type bridge con-name br0 ifname br0 \
    ipv4.method manual \
    ipv4.addresses "192.168.1.100/24" \
    ipv4.gateway "192.168.1.1" \
    connection.autoconnect-ports 1

nmcli connection add type ethernet port-type bridge \
    con-name br0-port-eth0 ifname eth0 controller br0

nmcli connection up br0

The connection.autoconnect-ports 1 setting ensures the bridge ports are brought up automatically. The second command creates a port profile that attaches eth0 to br0 as a bridge port .

# systemd-networkd: /etc/systemd/network/10-br0.netdev
[NetDev]
Name=br0
Kind=bridge

# /etc/systemd/network/10-br0.network
[Match]
Name=br0
[Network]
Address=192.168.1.100/24
Gateway=192.168.1.1

# /etc/systemd/network/20-eth0.network
[Match]
Name=eth0
[Network]
Bridge=br0

Restart systemd-networkd to apply. The .netdev file creates the bridge; the .network files assign the address and attach the port .


Complete Example Session

# ============================================
# PART 1: CREATE THE BRIDGE
# ============================================
ip link add name br0 type bridge
ip link set br0 up

# ============================================
# PART 2: FLUSH THE PHYSICAL INTERFACE
# ============================================
# Remove any IP address before attaching to the bridge
ip addr flush dev eth0

# ============================================
# PART 3: ATTACH THE PORT
# ============================================
ip link set eth0 master br0
ip link set eth0 up

# ============================================
# PART 4: ASSIGN THE BRIDGE ADDRESS
# ============================================
ip addr add 192.168.1.100/24 dev br0

# ============================================
# PART 5: ADD THE DEFAULT ROUTE
# ============================================
ip route add default via 192.168.1.1 dev br0

# ============================================
# PART 6: VERIFY THE BRIDGE
# ============================================
ip link show master br0
bridge link show
ip addr show br0

# ============================================
# PART 7: INSPECT THE FDB
# ============================================
bridge fdb show br br0
# FDB is empty until traffic flows

# ============================================
# PART 8: DISABLE STP (LOOP-FREE TOPOLOGY)
# ============================================
ip link set br0 type bridge stp_state 0

# ============================================
# PART 9: ATTACH A VIRTUAL MACHINE INTERFACE
# ============================================
ip link set tap0 master br0
ip link set tap0 up

# ============================================
# PART 10: PERSIST WITH NETPLAN
# ============================================
# Write the YAML configuration to /etc/netplan/
# Apply with: netplan apply
# Reboot and verify with: ip addr show br0; bridge link show

The ten parts demonstrate the complete lifecycle: create, flush, attach, address, route, verify, inspect, configure STP, extend with virtual ports, and persist. The critical step is Part 2: flushing the physical interface before attaching it to the bridge. An interface with an IP address attached to a bridge is not a port; it is a misconfiguration .


Quick Reference

Bridge Creation Commands

TaskCommand
Create bridgeip link add name br0 type bridge
Bring bridge upip link set br0 up
Flush port addressip addr flush dev eth0
Attach portip link set eth0 master br0
Detach portip link set eth0 nomaster
Assign bridge IPip addr add <addr>/<prefix> dev br0
Add default routeip route add default via <gw> dev br0
Delete bridgeip link delete br0

Verification Commands

TaskCommand
Show bridge portsip link show master br0
Show port statesbridge link show
Show FDBbridge fdb show br br0
Show STP stateip -d link show br0
Show bridge addressip addr show br0

Persistence Methods

SystemFile/MethodApply
Ubuntu/NetplanYAML under /etc/netplan/netplan apply
RHEL/nmclinmcli connection add type bridgenmcli connection up
systemd-networkd.netdev + .network filessystemctl restart systemd-networkd

Best Practices

✅ Do This:

# Flush the physical interface before attaching
ip addr flush dev eth0                                      # ✅

# Assign the IP to the bridge, not the port
ip addr add 192.168.1.100/24 dev br0                        # ✅

# Add the default route through the bridge
ip route add default via 192.168.1.1 dev br0                # ✅

# Verify port state is "forwarding"
bridge link show                                            # ✅

# Disable STP for loop-free leaf bridges
ip link set br0 type bridge stp_state 0                     # ✅

# Persist through the network manager
netplan apply / nmcli connection up                         # ✅

❌ Don’t Do This:

# Leave an IP on a bridge port
# The port is no longer a routed interface                    # ❌

# Attach a port before configuring the bridge (remote system)
# SSH session dies mid-configuration                          # ❌

# Assume the bridge works because it appears in ip link
# Verify port state and FDB                                   # ❌

# Enable STP on every bridge without checking for loops
# Unnecessary delay and potential blocking                    # ❌

# Forget to bring up the bridge and ports
ip link set br0 up; ip link set eth0 up                     # ❌

# Use brctl for new configurations
# Deprecated; iproute2 is the standard                        # ❌

Common Pitfalls

PitfallWhy It HappensFix
Host unreachable after attaching portIP still on physical interfaceip addr flush dev eth0
Remote SSH session diesAttached port before bridge had IP/routeConfigure bridge first; use script
Port not forwardingSTP blocking due to perceived loopCheck bridge link show; disable STP if no loop
FDB emptyNo traffic has flowed yetGenerate traffic to populate FDB
Bridge lost after rebootip link add is not persistentConfigure through network manager
Netplan config not appliedForgot netplan applyRun netplan apply after writing YAML

Real-World Examples

1. Create a basic bridge

ip link add name br0 type bridge
ip link set br0 up

2. Attach a physical port

ip addr flush dev eth0
ip link set eth0 master br0
ip link set eth0 up

3. Assign bridge IP and route

ip addr add 192.168.1.100/24 dev br0
ip route add default via 192.168.1.1 dev br0

4. Verify ports

bridge link show

5. Show FDB

bridge fdb show br br0

6. Disable STP

ip link set br0 type bridge stp_state 0

7. Attach a VM TAP device

ip link set tap0 master br0
ip link set tap0 up

8. Persist with Netplan

bridges:
  br0:
    interfaces: [eth0]
    addresses: [192.168.1.100/24]

9. Persist with nmcli

nmcli connection add type bridge con-name br0 ifname br0 ipv4.method manual ipv4.addresses "192.168.1.100/24"

10. Delete the bridge

ip link set eth0 nomaster
ip link delete br0

Visual

The Correct Sequence for a Remote System

┌──────────────────────────────────────────────────────────────┐
│  REMOTE-SAFE BRIDGE CREATION                                 │
│                                                              │
│  Step 1: Create bridge and assign IP                         │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ ip link add br0 type bridge                            │  │
│  │ ip link set br0 up                                     │  │
│  │ ip addr add 10.2.3.4/8 dev br0                         │  │
│  │ ip route append default via 10.0.0.1 dev br0           │  │
│  └────────────────────────────────────────────────────────┘  │
│                           │                                  │
│                           ▼                                  │
│  Step 2: Attach and flush in quick succession                │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ ip link set eth0 master br0                            │  │
│  │ ip addr del 10.2.3.4/8 dev eth0                        │  │
│  └────────────────────────────────────────────────────────┘  │
│                           │                                  │
│                           ▼                                  │
│  Result:                                                     │
│  - br0 has the IP and default route                          │
│  - eth0 is a port with no IP                                 │
│  - SSH session survives                                      │
│                                                              │
│  ⚠ If Step 2 runs before Step 1: lockout.                    │
└──────────────────────────────────────────────────────────────┘

Bridge Port States

┌──────────────────────────────────────────────────────────────┐
│  bridge link show OUTPUT                                     │
│                                                              │
│  3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500        │
│      master br0 state forwarding priority 32 cost 2          │
│                                                              │
│  4: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500        │
│      master br0 state forwarding priority 32 cost 2          │
│                                                              │
│  state forwarding = port passes traffic                      │
│  state blocking   = STP has blocked the port                 │
│  state disabled   = port is administratively down            │
│                                                              │
│  If a port is blocking and no loop exists:                   │
│  ip link set br0 type bridge stp_state 0                     │
└──────────────────────────────────────────────────────────────┘

Persistence Architecture

┌──────────────────────────────────────────────────────────────┐
│  RUNNING STATE vs PERSISTENT CONFIGURATION                   │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ RUNNING KERNEL (immediate)                             │  │
│  │ ip link add br0 type bridge                            │  │
│  │ ip link set eth0 master br0                            │  │
│  │ ip addr add ... dev br0                                │  │
│  │ ⚠ LOST ON REBOOT                                       │  │
│  └────────────────────────────────────────────────────────┘  │
│                           │                                  │
│                           │ Must match                       │
│                           ▼                                  │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ PERSISTENT CONFIGURATION                               │  │
│  │ Netplan YAML / nmcli profile / systemd-networkd        │  │
│  │                                                        │  │
│  │ netplan apply / nmcli connection up / restart service  │  │
│  │                                                        │  │
│  │ ✓ SURVIVES REBOOT                                      │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  Verify persistence: reboot, then bridge link show.         │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Create bridgeip link add name br0 type bridge
Flush portip addr flush dev eth0
Attach portip link set eth0 master br0
Assign IPip addr add <addr>/<prefix> dev br0
Default routeip route add default via <gw> dev br0
Verify portsbridge link show
Show FDBbridge fdb show br br0
STP controlip link set br0 type bridge stp_state 0/1
Ubuntu persistenceNetplan YAML + netplan apply
RHEL persistencenmcli connection add type bridge
systemd persistence.netdev + .network files
Remote lockout riskAttach port before bridge has IP/route

Key takeaways:

  • The IP address belongs on the bridge, not the port. Flushing the physical interface is not optional. A port with an address is a misconfiguration that produces connectivity failures that are difficult to diagnose .
  • On a remote system, configure the bridge before attaching the port. Assign the IP, set the route, and bring the bridge up first. Then attach the port and flush its address in quick succession. Reversing the order locks you out .
  • bridge link show reveals the port state. A port in forwarding state passes traffic. A port in blocking state does not. If no loop exists and a port is blocking, disable STP .
  • The FDB populates from traffic. An empty forwarding database immediately after creation is expected. Generate traffic to see learned MAC addresses .
  • Persistence is a separate operation. ip link add does not write to any configuration file. Netplan, nmcli, or systemd-networkd must be configured, and the persistence must be verified by rebooting.
  • brctl is deprecated. Use ip link and bridge from iproute2. The modern tools have more features, consistent syntax, and active maintenance .

Remember: Creating a bridge is three commands. Making it work is a sequence: create the bridge, give it the IP, flush the port, attach the port, bring everything up, verify the port state, and persist the configuration. The sequence matters most on a remote system, where a single misordered command severs the connection you are using to configure the system. Configure the bridge first, attach the port second, and verify with bridge link show and bridge fdb show. A bridge that appears in ip link but has a blocked port or an empty FDB is not forwarding, and the reason is in the state, not in the creation command.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!