Linux can act as much more than an endpoint on a network. With Linux bridges, network namespaces, and veth pairs, a single Linux host can become a complete virtual networking laboratory containing isolated hosts, virtual Ethernet links, switches, routing boundaries, and packet-capture points.
In the previous CorgiNetwork guide on Linux Network Namespaces, we created isolated networking environments and connected them with veth pairs.
That works extremely well when only two endpoints need a direct connection.
But what happens when three, five, or twenty virtual hosts need to share the same Layer 2 network?
This is where the Linux bridge becomes important.
According to the official Linux kernel Ethernet Bridge documentation, an Ethernet bridge operates at Layer 2 and forwards frames between network segments based on destination MAC addresses.
In practical terms, we can use a Linux bridge much like a virtual Ethernet switch.
This guide builds an end-to-end lab containing three network namespaces connected through a Linux bridge, then examines MAC learning, the forwarding database, ARP, packet capture, routing, Internet access, STP, VLAN filtering, and common bridge failures.
A Linux bridge is a software implementation of Ethernet bridging inside the Linux networking stack.
Conceptually:
Linux Bridge
/ | \
port1 port2 port3
| | |
Host A Host B Host C Unlike a traditional IP router, which primarily makes forwarding decisions using destination IP addresses and routing tables, an Ethernet bridge operates at Layer 2.
The official Linux Ethernet Bridging documentation describes a bridge as connecting network segments and filtering or forwarding frames based on destination MAC addresses.
Therefore, a useful simplified model is:
Router
↓
Destination IP
↓
Routing Table versus:
Bridge
↓
Destination MAC
↓
Forwarding Database This distinction is essential when troubleshooting Linux virtual networking.
A Linux bridge behaves in many ways like a software Ethernet switch.
A physical switch might connect:
PC1 ───┐
PC2 ───┼── Ethernet Switch
PC3 ───┘ A Linux bridge can connect:
Namespace 1 ───┐
Namespace 2 ───┼── Linux Bridge
Namespace 3 ───┘ The major difference is that the switch exists inside the Linux kernel rather than as a separate hardware device.
This makes bridges extremely useful for:
These technologies solve different problems.
A network namespace provides network-stack isolation.
As explained in CorgiNetwork’s Linux Network Namespaces guide, a namespace can have its own interfaces, routing tables, sockets, firewall context, and networking configuration.
A bridge provides Layer 2 connectivity.
Therefore:
Network Namespace
↓
Creates isolated endpoint while:
Linux Bridge
↓
Connects Ethernet endpoints Combining them gives us:
Namespace A
|
veth
|
+---------+
|
Linux Bridge
|
+---------+
|
veth
|
Namespace B This is the foundation of our lab.
A veth pair behaves like a virtual Ethernet cable.
The Linux veth(4) documentation describes veth devices as interconnected pairs: packets transmitted on one endpoint are immediately received by the other.
Conceptually:
veth-a <================> veth-b A bridge is different.
It can connect many Ethernet interfaces:
br0
/ | \
v1 v2 v3 Therefore:
veth = virtual cable
bridge = virtual switch This mental model makes Linux virtual networking much easier to understand.
We will build three isolated Linux network environments:
ns-red
10.10.10.11
|
veth-r
|
veth-r-br
|
|
+-------------+
| |
| br0 |
| |
+-------------+
/ \
veth-b-br veth-g-br
| |
veth-b veth-g
| |
ns-blue ns-green
10.10.10.12 10.10.10.13 All three namespaces will belong to:
10.10.10.0/24 Our Linux host will act as the virtual switch.
Before creating anything, inspect the host:
ip -br link Then:
ip -br addr Check existing bridges:
ip link show type bridge The ip-link(8) documentation supports bridge as a Linux link type and provides the interface used to create and configure bridge devices.
Never assume that br0 is unused on a production machine.
Virtualization software such as KVM, libvirt, Docker, LXD, or other platforms may already have created bridges.
Create RED:
sudo ip netns add ns-red Create BLUE:
sudo ip netns add ns-blue Create GREEN:
sudo ip netns add ns-green Verify:
ip netns list You should see:
ns-red
ns-blue
ns-green Bring loopback up inside each namespace:
sudo ip netns exec ns-red ip link set lo up
sudo ip netns exec ns-blue ip link set lo up
sudo ip netns exec ns-green ip link set lo up We now have three isolated network stacks.
Create:
sudo ip link add br0 type bridge Bring it up:
sudo ip link set br0 up Verify:
ip link show br0 You can also list all bridge devices:
ip link show type bridge At this stage:
Linux Host
|
br0 exists, but no ports are connected to it.
Create:
sudo ip link add veth-r-br type veth peer name veth-r Conceptually:
veth-r-br <============> veth-r Move the namespace-facing endpoint:
sudo ip link set veth-r netns ns-red The topology becomes:
Host ns-red
veth-r-br <=============> veth-r Now attach the host endpoint to the bridge:
sudo ip link set veth-r-br master br0 Bring it up:
sudo ip link set veth-r-br up Inside RED:
sudo ip netns exec ns-red ip link set veth-r up Create:
sudo ip link add veth-b-br type veth peer name veth-b Move:
sudo ip link set veth-b netns ns-blue Attach:
sudo ip link set veth-b-br master br0 Bring up the host side:
sudo ip link set veth-b-br up Then:
sudo ip netns exec ns-blue ip link set veth-b up Create:
sudo ip link add veth-g-br type veth peer name veth-g Move:
sudo ip link set veth-g netns ns-green Attach:
sudo ip link set veth-g-br master br0 Bring it up:
sudo ip link set veth-g-br up Then:
sudo ip netns exec ns-green ip link set veth-g up Now we have:
br0
/ | \
/ | \
veth-r-br veth-b-br veth-g-br
| | |
veth-r veth-b veth-g
| | |
ns-red ns-blue ns-green The bridge ports can be inspected with the bridge utility documented in the official bridge(8) manual.
Run:
bridge link show You can also use:
ip link show master br0 to show interfaces attached to br0.
RED:
sudo ip netns exec ns-red \
ip addr add 10.10.10.11/24 dev veth-r BLUE:
sudo ip netns exec ns-blue \
ip addr add 10.10.10.12/24 dev veth-b GREEN:
sudo ip netns exec ns-green \
ip addr add 10.10.10.13/24 dev veth-g Verify:
sudo ip netns exec ns-red ip -br addr sudo ip netns exec ns-blue ip -br addr sudo ip netns exec ns-green ip -br addr Our virtual LAN is now:
br0
|
10.10.10.0/24 LAN
/ | \
/ | \
10.10.10.11 10.10.10.12 10.10.10.13
RED BLUE GREEN Notice that we have not assigned an IP address to br0.
That is intentional.
A Linux bridge does not require an IP address merely to forward Ethernet frames.
From RED:
sudo ip netns exec ns-red \
ping 10.10.10.12 The packet path is approximately:
ns-red
↓
veth-r
↓
veth-r-br
↓
br0
↓
veth-b-br
↓
veth-b
↓
ns-blue Test GREEN:
sudo ip netns exec ns-red \
ping 10.10.10.13 Then BLUE to GREEN:
sudo ip netns exec ns-blue \
ping 10.10.10.13 If all three work, the bridge is successfully switching Ethernet frames between the namespaces.
Ethernet bridges need to know where destination MAC addresses are located.
Suppose RED sends traffic toward BLUE.
Initially, the bridge sees a frame arriving from:
RED MAC on:
veth-r-br The bridge can learn:
RED MAC → veth-r-br When BLUE responds:
BLUE MAC → veth-b-br The bridge gradually builds a forwarding database.
Conceptually:
MAC Address Bridge Port
RED-MAC → veth-r-br
BLUE-MAC → veth-b-br
GREEN-MAC → veth-g-br This is one of the most important concepts in Ethernet switching.
Linux exposes the forwarding database through the bridge command.
Run:
bridge fdb show br br0 The official bridge(8) documentation provides commands for inspecting and manipulating bridge forwarding database entries, VLAN information, multicast databases, and bridge ports.
Generate traffic between all namespaces and inspect the FDB again:
bridge fdb show br br0 You should see dynamic MAC entries associated with bridge ports.
This gives you direct evidence of what the bridge has learned.
Do not confuse:
Bridge FDB with:
ARP / neighbor table They solve different problems.
The bridge FDB maps:
MAC address
↓
bridge port The ARP/neighbor table maps:
IP address
↓
MAC address For example:
ARP
10.10.10.12
↓
AA:BB:CC:DD:EE:FF while:
FDB
AA:BB:CC:DD:EE:FF
↓
veth-b-br Together:
IP
↓
MAC
↓
Bridge Port This distinction becomes extremely valuable during troubleshooting.
From RED:
sudo ip netns exec ns-red ip neigh After pinging BLUE you might see an entry representing:
10.10.10.12 → BLUE MAC Compare with:
bridge fdb show br br0 You now have two perspectives:
Namespace:
IP → MAC
Bridge:
MAC → Port If you’re troubleshooting unusual neighbor behavior on a multi-NIC host, CorgiNetwork’s Linux ARP Flux and Multiple NICs on the Same Subnet explores the ARP side in greater depth.
Run:
sudo tcpdump -eni veth-r-br arp Then clear RED’s neighbor cache:
sudo ip netns exec ns-red ip neigh flush all Generate:
sudo ip netns exec ns-red \
ping -c 1 10.10.10.12 You should see ARP traffic before normal IP communication.
Conceptually:
RED:
Who has 10.10.10.12?
↓
Broadcast Ethernet Frame
↓
br0
↙ ↘
BLUE GREEN BLUE responds with its MAC address.
The bridge then gains additional information about the participating endpoints.
Try:
sudo tcpdump -eni br0 Then generate:
sudo ip netns exec ns-red ping 10.10.10.12 Packet capture helps answer questions such as:
For deeper packet analysis, continue with CorgiNetwork’s Wireshark Explained guide.
Our bridge currently acts only as a Layer 2 switch.
We can make the Linux host itself participate in the virtual LAN.
Assign:
sudo ip addr add 10.10.10.1/24 dev br0 Verify:
ip -br addr show br0 Now:
Linux Host
10.10.10.1
|
br0
/ | \
/ | \
RED BLUE GREEN
.11 .12 .13 Test from RED:
sudo ip netns exec ns-red \
ping 10.10.10.1 The host is now another Layer 3 participant on the bridged network.
Suppose a physical interface becomes a bridge port.
A common Linux bridge architecture is:
Physical NIC
↓
br0 Once the interface participates as a bridge port, Layer 3 configuration is generally placed on the bridge interface rather than treated as ordinary independent Layer 3 configuration on the enslaved port.
Conceptually:
Wrong mental model:
eth0 → IP
|
br0
Better model:
eth0
|
br0 → IP This distinction prevents many bridge configuration problems.
Because:
br0 = 10.10.10.1 we can configure it as the gateway.
RED:
sudo ip netns exec ns-red \
ip route add default via 10.10.10.1 BLUE:
sudo ip netns exec ns-blue \
ip route add default via 10.10.10.1 GREEN:
sudo ip netns exec ns-green \
ip route add default via 10.10.10.1 Inspect:
sudo ip netns exec ns-red ip route Expected conceptually:
10.10.10.0/24 dev veth-r
default via 10.10.10.1 Now the Linux host can become the Layer 3 gateway for destinations outside the virtual LAN.
Check:
sysctl net.ipv4.ip_forward For a lab where the host intentionally operates as a router:
sudo sysctl -w net.ipv4.ip_forward=1 The architecture becomes:
Namespaces
↓
br0
↓
Linux Host Routing
↓
Physical Interface
↓
Upstream Router At this point we have combined:
Layer 2 bridging
+
Layer 3 routing inside one Linux machine.
Assume the Linux host has an Internet-facing interface such as:
eth0 and the upstream router has no route for:
10.10.10.0/24 The host may need source NAT.
The conceptual packet path becomes:
ns-red
10.10.10.11
↓
br0
↓
Linux Routing
↓
Firewall/NAT
↓
eth0
↓
Internet Before changing firewall configuration, inspect the current nftables rules:
sudo nft list ruleset Do not flush an existing production firewall merely to provide namespace connectivity.
If the upstream router already knows:
10.10.10.0/24 via LINUX_HOST NAT may not be necessary.
Always distinguish routing from NAT.
This is a critical troubleshooting concept.
Traffic from:
RED → BLUE on the same subnet is bridged:
RED
↓
br0
↓
BLUE Traffic from:
RED → Internet uses:
RED
↓
br0
↓
Linux IP routing
↓
uplink Therefore:
Same Layer 2 network
↓
Bridge forwarding while:
Different IP network
↓
Routing Do not troubleshoot both paths as though they were identical.
Ethernet loops can be dangerous.
Consider:
+------ Switch A ------+
| |
| |
+------ Switch B ------+ Layer 2 loops can create repeated frame forwarding and broadcast storms.
Linux bridges support Spanning Tree Protocol configuration.
The official ip-link(8) bridge documentation exposes stp_state as a bridge option.
Inspect detailed bridge information:
ip -d link show br0 A bridge can be created with STP enabled, for example:
sudo ip link add br1 type bridge stp_state 1 Whether STP should be enabled depends on topology.
For a simple isolated lab with no Layer 2 loops, it may not be necessary.
For networks connected to external switching infrastructure, loop prevention must be designed deliberately.
Linux bridges can also be VLAN-aware.
The ip-link bridge configuration includes:
vlan_filtering and the bridge(8) utility provides VLAN management commands.
Conceptually:
br0
VLAN-aware bridge
/ \
Port A Port B
VLAN 10 VLAN 20 Inspect:
bridge vlan show A bridge with VLAN filtering can provide virtual switching architectures that would otherwise require several separate bridges.
This is important for:
A dedicated CorgiNetwork article should cover this separately because bridge VLAN filtering deserves a complete end-to-end lab.
Inspect:
bridge link show The official bridge(8) manual exposes bridge-port configuration including state, learning, flooding, STP-related parameters, hairpin behavior, multicast handling, isolation, and other attributes.
This makes bridge link one of the most important diagnostic commands when a port appears connected but frames do not behave as expected.
Linux bridge ports normally participate in MAC learning.
Conceptually:
Frame enters port 1
Source MAC = AA:AA:AA:AA:AA:AA
Bridge learns:
AA:AA:AA:AA:AA:AA → port 1 Later, a frame destined for that MAC can be sent specifically toward port 1 rather than being flooded unnecessarily.
Inspect:
bridge fdb show Generate traffic:
sudo ip netns exec ns-red ping 10.10.10.12 Then inspect again:
bridge fdb show br br0 This is a practical way to watch the virtual switch learn.
Suppose the bridge receives a frame for a destination MAC it has not learned.
Conceptually:
Unknown destination MAC
↓
br0
↙ ↓ ↘
port1 port2 port3 The bridge may flood the frame to eligible ports.
Once the destination responds and its location is learned:
Destination MAC
↓
Known bridge port future traffic can be forwarded more selectively.
Understanding this explains why packet captures sometimes show traffic on several bridge ports during initial communication.
ARP requests use Ethernet broadcast.
Therefore:
RED
↓
ARP request
↓
br0
↙ ↘
BLUE GREEN This is normal.
If broadcast traffic fails to reach another namespace, inspect:
bridge link show and:
tcpdump before modifying IP routes.
The problem may be Layer 2, not Layer 3.
Start with the endpoint.
Check RED:
sudo ip netns exec ns-red ip -br link Then:
sudo ip netns exec ns-red ip -br addr Check its route:
sudo ip netns exec ns-red ip route Then inspect the host-side port:
ip link show veth-r-br Check bridge membership:
ip link show master br0 Then:
bridge link show Finally:
bridge fdb show br br0 This progresses logically from:
Endpoint
↓
veth
↓
bridge port
↓
bridge
↓
destination A common mistake is creating:
veth-r-br but forgetting:
sudo ip link set veth-r-br master br0 Check:
ip link show master br0 If the interface is missing, the bridge cannot forward traffic through it.
Check:
ip -br link and inside the namespace:
sudo ip netns exec ns-red ip -br link Remember that both ends matter:
veth-r
↕
veth-r-br Do not assume that creating a veth pair automatically completes the network configuration.
From RED:
sudo ip netns exec ns-red ip neigh Capture:
sudo tcpdump -eni veth-r-br arp Then:
sudo tcpdump -eni veth-b-br arp Generate:
sudo ip netns exec ns-red ping 10.10.10.12 Ask:
Does ARP leave RED?
Does it reach br0?
Does it reach BLUE?
Does BLUE respond?
Does the response return? This immediately narrows the problem.
Inspect:
bridge fdb show br br0 Identify the destination MAC:
sudo ip netns exec ns-red ip neigh Then correlate:
IP
↓
MAC
↓
FDB
↓
Bridge Port This technique is much more powerful than repeatedly running ping.
If:
RED → BLUE works but:
RED → Internet does not, the bridge is probably functioning.
Move the investigation to Layer 3:
sudo ip netns exec ns-red ip route Then:
sudo ip netns exec ns-red \
ip route get 8.8.8.8 Check the gateway:
sudo ip netns exec ns-red ping 10.10.10.1 Then host forwarding:
sysctl net.ipv4.ip_forward Then:
ip route and:
sudo nft list ruleset This prevents wasting time troubleshooting a working bridge.
Suppose your Linux host has:
eth0 → ISP A
eth1 → ISP B The namespace may correctly reach:
br0 while the host sends traffic through the wrong external interface.
Check:
ip route get 8.8.8.8 Then:
ip rule and:
ip route show table all If the wrong NIC is selected, use CorgiNetwork’s Linux Traffic Uses the Wrong Interface guide.
For deliberate multi-table routing, continue with Linux Policy Routing with ip rule and Multiple Routing Tables.
rp_filterBridging itself is Layer 2, but once the host begins routing between the bridge and other networks, reverse-path filtering can become relevant.
For complex multi-homed or asymmetric environments, inspect:
sysctl -a 2>/dev/null | grep '\.rp_filter' Do not disable it blindly.
If asymmetric return paths are involved, use CorgiNetwork’s Linux rp_filter and Asymmetric Routing to understand how strict and loose reverse-path validation affect traffic.
A particularly powerful design is:
Namespace
↓
veth
↓
Linux Bridge
↓
Host Layer 3
↓
ip rule
↓
Custom Routing Table
↓
Selected Gateway For example:
ns-red
↓
ISP A
ns-blue
↓
ISP B Policy routing can select the correct uplink based on source address, firewall mark, or other supported policy criteria.
CorgiNetwork’s Linux Policy Routing guide provides the routing-table layer required for this architecture.
On Ubuntu servers, persistent bridge configuration may be managed using Netplan rather than manually running ip commands after every boot.
This becomes especially relevant when combining:
Bridge
+
Multiple physical NICs
+
Multiple gateways
+
Policy routing For the gateway side of that architecture, see CorgiNetwork’s Netplan Multiple Default Routes and Policy Routing.
A future dedicated guide can cover persistent Netplan bridge configuration separately.
When Linux bridge networking fails, use this order.
ip netns list sudo ip netns exec NAME ip -br link sudo ip netns exec NAME ip -br addr ip -br link ip link show type bridge ip link show master br0 bridge link show bridge fdb show br br0 sudo ip netns exec NAME ip neigh sudo tcpdump -eni PORT arp sudo tcpdump -eni PORT icmp sudo ip netns exec NAME ip route ip route ip rule
ip route show table all sudo nft list ruleset This follows the packet from source to destination instead of guessing.
Namespace A cannot reach Namespace B
|
v
Interface UP?
/ \
NO YES
| |
Bring UP v
Correct IP?
/ \
NO YES
| |
Fix IP v
veth peer UP?
/ \
NO YES
| |
Bring UP v
Attached to br0?
/ \
NO YES
| |
Attach v
Bridge UP?
/ \
NO YES
| |
Bring UP v
ARP crossing?
/ \
NO YES
| |
Debug L2 v
FDB correct?
/ \
NO YES
| |
Debug MAC v
Check
destination Remove the namespaces:
sudo ip netns del ns-red
sudo ip netns del ns-blue
sudo ip netns del ns-green Delete the bridge:
sudo ip link del br0 Because the veth namespace endpoints are associated with the namespaces, namespace deletion also cleans up the relevant virtual Ethernet pairs.
Verify:
ip netns list Then:
ip link show type bridge A Linux bridge is a software Ethernet bridge implemented in the Linux networking stack. It connects Ethernet interfaces and forwards frames primarily according to MAC-address information.
No.
A bridge primarily operates at Layer 2 using Ethernet/MAC information.
A router operates at Layer 3 using IP routing decisions.
A Linux host can perform both functions simultaneously.
No.
A namespace creates an isolated networking environment.
A bridge connects Layer 2 endpoints.
They are commonly used together.
A veth pair is a pair of interconnected virtual Ethernet interfaces.
One endpoint can be placed inside a namespace while the other is connected to a Linux bridge.
Not merely to switch Ethernet frames.
Assigning an IP address to the bridge allows the Linux host itself to participate at Layer 3 on that network.
Use:
sudo ip link add br0 type bridge
sudo ip link set br0 up Use:
sudo ip link set INTERFACE master br0 Use:
bridge link show or:
ip link show master br0 Use:
bridge fdb show br br0 The Forwarding Database associates learned MAC addresses with bridge ports so the bridge can make Layer 2 forwarding decisions.
Yes.
Linux bridges support VLAN-aware configurations, including VLAN filtering and per-port VLAN configuration.
Yes.
Linux bridge configuration supports Spanning Tree Protocol settings.
Yes.
Typically the Linux host must provide appropriate Layer 3 routing, firewall forwarding, and either an upstream route or NAT depending on the network architecture.
Linux bridges, network namespaces, and veth pairs form one of the most useful combinations in advanced Linux networking.
A network namespace gives us:
Isolated network stack A veth pair gives us:
Virtual Ethernet cable A Linux bridge gives us:
Virtual Ethernet switch Together:
Linux Host
br0
/ | \
/ | \
veth veth veth
| | |
Namespace Namespace Namespace
RED BLUE GREEN The official Linux kernel Ethernet Bridge documentation provides the underlying Layer 2 bridge model, while ip-link(8) documents creation and configuration of bridge interfaces and the bridge(8) utility exposes bridge ports, FDB entries, VLAN configuration, multicast information, and other bridge state.
The most important commands from this lab are:
ip link add br0 type bridge
ip link set br0 up
ip link add veth-host type veth peer name veth-ns
ip link set veth-ns netns NAME
ip link set veth-host master br0
bridge link show
bridge fdb show br br0
bridge vlan show But troubleshooting should always follow the packet.
If traffic never crosses the veth pair, investigate the endpoint.
If ARP reaches the bridge but not another port, investigate Layer 2 bridge state.
If namespace-to-namespace communication works but Internet access fails, investigate routing, forwarding, firewall rules, NAT, and the upstream return path.
For namespace fundamentals, use CorgiNetwork’s Linux Network Namespaces guide.
For multi-homed routing problems, use Linux Traffic Uses the Wrong Interface.
For multiple routing tables, continue with Linux Policy Routing with ip rule and Multiple Routing Tables.
For reverse-path problems, see Linux rp_filter and Asymmetric Routing.
For unusual ARP behavior, see Linux ARP Flux and Multiple NICs on the Same Subnet.
And for persistent Ubuntu multi-gateway configurations, use Netplan Multiple Default Routes and Policy Routing.
This extends the CorgiNetwork advanced networking cluster:
Linux Routing Problems
↓
Multiple Default Routes
↓
ARP Flux
↓
rp_filter / Asymmetric Routing
↓
Policy Routing
↓
VRF
↓
Network Namespaces
↓
veth
↓
Linux Bridge
↓
Bridge FDB / MAC Learning
↓
VLAN-Aware Linux Bridge The next logical article is Linux Bridge VLAN Filtering Explained: Build a VLAN-Aware Virtual Switch with Network Namespaces, where we can build VLAN 10 and VLAN 20 on a single Linux bridge, configure access and trunk ports, inspect bridge vlan show, and troubleshoot inter-VLAN isolation.
AI Agents as Code: Managing AI Agent Configurations, Prompts and Tools with Git AI agents…
Data Center Power Distribution Architecture: UPS, PDU, Redundancy and Rack Power Explained Data center power…
DNSSEC Troubleshooting: Fix SERVFAIL & DNSKEY Errors DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY…
Terraform State Management Explained: Remote State, Locking, Backends and Best Practices Terraform makes infrastructure reproducible…
RTO vs RPO Explained: Disaster Recovery Objectives, Examples and Planning A disaster recovery plan is…
Network Intrusion Detection Explained: NIDS, Sensors, Signatures and Traffic Analysis A firewall can decide whether…