Overview
A Linux server can act as a switch, a router or both. People often reach for a bridge when what they need is a router, and the difference decides how safe the network is. A bridge joins networks into one. A router keeps them separate and forwards traffic between them.
This article explains how a Linux bridge works, then covers six designs for connecting an internal server network to an external network through one Linux gateway. Each design has a diagram, commands for Ubuntu and CentOS-family systems, and its security risks and limits.
| Goal | Design | Bridge needed? |
|---|---|---|
| Internal servers reach the internet and stay hidden behind the gateway | Routing and NAT (Scenario B) | Only to join several internal ports or VMs |
| Internal servers appear as normal hosts on the external network | Layer 2 bridge (Scenario A) | Yes |
| Internal servers keep routable addresses in a separate network | Routing without NAT (Scenario C) | No |
| Virtual machines or containers on the gateway | Bridge or NAT network (Scenario D) | Yes |
| Groups of servers kept apart on shared ports | VLAN-aware bridge (Scenario E) | Yes |
| A gateway cabled to an external switch and an internal switch | Routed gateway or inline bridge (Scenario F) | Only for an inline bridge |
If you read only one part, read Scenario B and the security section. Together they cover the most common design: one gateway with several servers behind it.
How a Linux bridge works
A Linux bridge is a virtual switch inside the kernel. It forwards Ethernet frames between the interfaces attached to it, based on destination MAC addresses (Red Hat Developer). It works at Layer 2 and never looks at IP addresses.
The bridge forwards a frame to the port where it last saw the destination MAC address. The host talks to the bridge network through br0.
| Term | What it means |
|---|---|
| Port | Any interface attached to the bridge with ip link set eth1 master br0: a physical NIC, a VLAN interface, a VM’s tap device or a container’s veth. |
br0 |
The bridge’s own interface. The host’s IP address belongs here, not on the ports. |
| Forwarding database (FDB) | The table of MAC address to port, learned from the source address of received frames. Show it with bridge fdb show br br0. |
| Flooding | A frame with a broadcast destination, or an unknown one, goes out of every other port. The per-port flood flag controls unknown unicast and is on by default (bridge(8)). |
| Learning | Each port learns MAC addresses from received traffic. The per-port learning flag is on by default. |
| Ageing | Learned entries expire. The default is 300 seconds (NetworkManager). |
| STP | Spanning Tree Protocol blocks redundant links to prevent loops and broadcast storms (kernel docs). Linux bridges do not support RSTP (Red Hat Developer). |
| VLAN filtering | Lets one bridge keep several VLANs apart. It is disabled by default (kernel docs). |
Bridge, router and NAT compared
| Bridge | Router | NAT | |
|---|---|---|---|
| Works on | MAC addresses (Layer 2) | IP addresses (Layer 3) | IP addresses and ports, rewritten in transit |
| Networks | Joins segments into one broadcast domain | Keeps separate subnets, each with its own broadcast domain | Sits on a router and hides one side behind the other |
| Host addresses | All hosts share one subnet | One subnet per side | Internal addresses are replaced by the gateway’s external address |
| On Linux | ip link add type bridge |
net.ipv4.ip_forward=1 plus routes |
nftables masquerade |
Two facts explain most bridging problems:
- A port no longer handles IP traffic directly. Move the IP address from the NIC to the bridge.
- A bridge never changes IP addresses. It cannot make a private subnet reachable from another network. Only routing and NAT can.
The example network: four servers, one uplink
All scenarios use one small example network. Only one of its four servers, called the gateway here, has a NIC on the external network. The other three sit on an internal network behind it and reach the outside only through the gateway. The same ideas apply to any number of servers.
Server 1 is the only machine with a foot in both networks. Every scenario below is a choice about what it does with traffic between its two NICs.
Questions to settle before choosing a design:
- Must the internal servers be reachable from outside, or only able to reach out (updates, time, DNS)?
- Can the external network give you more than one address, or a routed subnet?
- Who runs the external network? Port security, 802.1X or segmentation policy there can rule out bridging.
- How many internal ports does the gateway have: one NIC to a switch, or several NICs straight to servers?
- Does the gateway also run virtual machines or containers?
- Is it acceptable, under your security policy, for these servers to reach the internet at all?
All examples use documentation address ranges and these names. Real interface names are often enp3s0 rather than eth0; check yours with ip -br link.
| Item | Value |
|---|---|
| External network | 192.0.2.0/24, upstream router 192.0.2.1 |
| Gateway uplink NIC | eth0, 192.0.2.10 |
| Gateway internal NIC | eth1 |
| Internal network | 10.10.10.0/24, gateway address 10.10.10.1 |
| Other servers | Server 2: 10.10.10.2, Server 3: 10.10.10.3, Server 4: 10.10.10.4 |
Scenario A: bridge the uplink and the internal network (Layer 2)
Bridging eth0 and eth1 merges the external and internal networks into one. Every server then lives on the external subnet, as if it were plugged into the upstream switch.
The bridge passes frames through. It makes no routing decision, so the upstream router sees each server directly.
Four things must be true for this to work:
- Servers 2 to 4 need addresses in the external subnet (192.0.2.0/24), static or from the upstream DHCP server. Their old 10.10.10.x addresses are useless, because the upstream router knows nothing about that subnet.
- The upstream port must accept several MAC addresses. Port security or 802.1X on that port will block the extra servers.
- The uplink must be wired Ethernet. A Wi-Fi interface in client mode cannot normally be bridged. The kernel only allows it when both sides negotiate four-address mode (kernel commit).
- The gateway’s own IP address moves from
eth0tobr0.
On the gateway, as a temporary setup that is lost on reboot:
ip link add br0 type bridge
ip link set br0 up
ip addr flush dev eth0
ip link set eth0 master br0
ip link set eth1 master br0
ip link set eth0 up
ip link set eth1 up
ip addr add 192.0.2.10/24 dev br0
ip route add default via 192.0.2.1ip addr add 192.0.2.12/24 dev eth0
ip route add default via 192.0.2.1No ip_forward and no NAT are needed, because the gateway never routes. It only switches frames.
| Advantages | Drawbacks |
|---|---|
| Simplest for the servers: no NAT, no routing | One broadcast domain: ARP, DHCP and discovery traffic cross between both sides |
| Each server has its own external identity and can accept inbound connections | The internal servers sit directly on the external network, with no security boundary in between |
| Works for non-IP protocols too | The gateway’s IP firewall does not see bridged frames by default (see the security section) |
| No address translation to troubleshoot | The upstream must supply addresses and allow several MACs per port |
Use it when the external network is trusted and managed by you, and the gateway is just acting as a small switch. Avoid it when the external side is the internet or any less trusted network.
Scenario B: two networks, routing and NAT (recommended)
The gateway keeps the internal network separate and forwards traffic between the two sides. NAT, here masquerade, rewrites the internal source addresses to the gateway’s external address, so the external network sees one host.
The gateway is a router here, not a switch. The two sides stay separate, and the firewall decides what crosses.
Do you need a bridge here? Not if the gateway has a single internal NIC. Put the address on eth1 and skip step 1, then use eth1 wherever the firewall rules below say br0. Use a bridge when the gateway has several internal ports you want to act as one switch, or when VMs and containers join the internal network. The steps below use br0 for the internal side.
Step 1: build the internal bridge
ip link add br0 type bridge
ip link set br0 up
ip link set eth1 master br0
ip link set eth1 up
ip addr add 10.10.10.1/24 dev br0Step 2: turn on forwarding
The uplink NIC keeps its own address and default route. Then enable IPv4 forwarding, now and after reboot (OneUptime):
sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-ip-forward.conf
sysctl --systemStep 3: firewall and NAT with nftables
The masquerade rule is the standard form from the nftables wiki. Masquerade suits an uplink whose address may change. With a fixed address, snat to <address> does the same job. The rules around it default to drop, so only listed traffic passes.
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iifname "lo" accept
ct state established,related accept
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
iifname "br0" tcp dport 22 accept
# add DNS here only if the gateway runs a DNS forwarder:
# iifname "br0" udp dport 53 accept
# iifname "br0" tcp dport 53 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
iifname "br0" oifname "eth0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oifname "eth0" masquerade
}
}nft -c -f /etc/nftables.conf # check only, applies nothing
nft -f /etc/nftables.conf # loadThe distribution’s nftables service loads its own rules file at boot, and the file’s location differs between distributions. If the uplink uses DHCP, also allow UDP port 68 on eth0. These rules cover IPv4 NAT. IPv6 is a separate path: the inet table filters both, but only IPv4 is translated.
On CentOS, RHEL, AlmaLinux and Rocky Linux, firewalld manages the firewall. The CentOS section further down shows the same NAT setup with firewalld.
Step 4: point the other servers at the gateway
On each of Servers 2, 3 and 4 (use .3 and .4 for the others):
ip addr add 10.10.10.2/24 dev eth0
ip route add default via 10.10.10.1They also need a DNS resolver they can reach. Either use the upstream resolver, which the forward rule above already allows through, or run a forwarder such as dnsmasq on the gateway.
Step 5: test
- From Server 2, ping 10.10.10.1, then 192.0.2.1, then a public address.
- Resolve a hostname, to prove DNS works.
- On the gateway, run
tcpdump -ni eth0while pinging from Server 2. The source must show 192.0.2.10, not 10.10.10.2.
Inbound access is closed by default, because new connections from outside are dropped. You can open a single service with destination NAT, but every such rule exposes a server. Put a reverse proxy in front instead where you can.
| Advantages | Drawbacks |
|---|---|
| Internal addresses and topology stay hidden | The gateway is a single point of failure and a high-value target |
| One external address is enough | Inbound access needs extra rules |
| Default-drop forwarding gives a real security boundary | NAT state (conntrack) has finite capacity |
| Needs nothing from the upstream network | Some protocols that carry IP addresses in their payload need extra help |
Scenario C: route without NAT
If the upstream network can route a subnet to you, you can skip NAT. The gateway still routes between two separate networks, but the internal servers keep their own addresses and the outside world sees them as they are.
The setup is Scenario B without the masquerade rule. Keep forwarding enabled and keep the default-drop forward chain. Two things change:
- The upstream router needs a route to the internal subnet, pointing at the gateway’s external address. In plain words: “to reach 10.10.10.0/24, send traffic to 192.0.2.10”. The syntax depends on the router.
- The internal addresses must be valid on the upstream side. Private ranges like 10.10.10.0/24 only work inside a network that carries your route. To reach the public internet this way, you need a public subnet routed to you by your provider.
The most common failure is outbound packets that leave but never get replies. The upstream router has no return route, so it discards the answers. Either add the route or go back to NAT.
| Advantages | Drawbacks |
|---|---|
| No NAT state to track or troubleshoot | Needs the upstream owner to add and maintain a route |
| Inbound connections work without port forwarding | Servers are reachable from outside as far as your forward rules allow |
| Logs show real source addresses | One mistake in the forward rules exposes a server directly |
Scenario D: virtual machines and containers on the gateway
Hypervisors and container runtimes attach each guest to a bridge through a tap or veth device. The bridge on the gateway can then be set up in two ways, and they map onto Scenarios A and B.
Isolated bridge with NAT. This is Scenario B in miniature. libvirt’s default network works this way: the host gets an isolated bridge, virbr0, with no physical interfaces attached. Guests reach the outside through NAT and forwarding, and libvirt adds the iptables rules for it (libvirt wiki). Guests are hidden from the external network.
Bridged guests. This is Scenario A. The uplink NIC joins the bridge, and each guest appears on the external network with its own MAC and IP address. The upstream port must accept all those MAC addresses. It cannot work over Wi-Fi, because wireless interfaces cannot be attached to a Linux host bridge (libvirt wiki).
When the gateway also runs Docker
Docker interacts with the firewall in ways that surprise gateway builders (Docker docs):
- Its default bridge network needs IP forwarding. If Docker enables forwarding itself, it also sets the
FORWARDchain policy in the iptablesfiltertable toDROP. - Rules you append to
FORWARDrun after Docker’s rules. Put your own rules in theDOCKER-USERchain, which Docker processes first. - By default, any external source address can connect to a published port.
After installing Docker on a gateway, retest forwarding and review the rules.
The left pattern hides guests behind the gateway. The right pattern puts each guest on the external network.
Scenario E: one bridge, several VLANs
Use a VLAN-aware bridge when the gateway’s internal ports carry servers of different trust levels, for example application servers and a management network, and you want them kept apart on the same hardware. VLAN filtering has been available since kernel 3.8 (Red Hat Developer).
The bridge keeps the two VLANs apart at Layer 2. Routing between them happens only at the gateway, where the firewall rules apply.
This example puts eth1 in VLAN 10 and eth2 in VLAN 20 as access ports, so the servers on them send and receive untagged frames:
ip link set br0 type bridge vlan_filtering 1
ip link set eth1 master br0
ip link set eth2 master br0
bridge vlan add dev eth1 vid 10 pvid untagged
bridge vlan add dev eth2 vid 20 pvid untagged
bridge vlan del dev eth1 vid 1
bridge vlan del dev eth2 vid 1
bridge vlan showNew ports start in the default VLAN 1. The two del lines take the access ports out of it. A trunk port to another switch carries several tagged VLANs. For example, bridge vlan add dev eth3 vid 10-20 allows VLANs 10 to 20 on eth3.
VLANs only separate traffic at Layer 2. For the gateway to route between them, or out to the internet, give each VLAN its own interface on the bridge:
bridge vlan add dev br0 vid 10 self
ip link add link br0 name br0.10 type vlan id 10
ip addr add 10.10.10.1/24 dev br0.10
ip link set br0.10 upRepeat for VLAN 20 with its own subnet. Then the same Scenario B rules apply, with br0.10 and br0.20 in place of br0. Because the forward chain defaults to drop, the VLANs cannot reach each other unless you allow it.
| Advantages | Drawbacks |
|---|---|
| Real separation of server groups on shared ports and one bridge | A mistake in the VLAN table silently joins or splits groups |
| Inter-VLAN traffic passes through your firewall rules | VLANs alone are not a security boundary, only the firewall rules between them are |
| Scales to more groups without more NICs | Ports left in the default VLAN 1 stay shared |
A related tool is filtering inside the bridge itself. nftables has a bridge family for rules on Layer 2 frames, and it works without the br_netfilter module (kernel docs). It is the right place for rules that must apply to bridged traffic, as in Scenario A.
Scenario F: the gateway between an external switch and an internal switch
In many server rooms the gateway is cabled to two physical switches. One carries the external network and the other carries the internal network. The gateway has at least two NICs: eth0 to the external switch and eth1 to the internal switch. What the gateway does between them decides the design.
The gateway’s two NICs sit on separate switches. The dashed path shows the one mistake that matters most in the bridged design.
Routed gateway (recommended). eth0 and eth1 stay separate interfaces with their own addresses. The gateway forwards between them and applies the firewall and, if wanted, NAT. This is Scenario B or C with a real switch on each side. The two switches never share a broadcast domain, so a loop or a broadcast storm on one side cannot spread to the other.
Bridged gateway. br0 holds eth0 and eth1. The gateway becomes a two-port switch between the two switches, which then form one Layer 2 network. This is Scenario A with physical switches on both sides. It suits two jobs: extending one network across two switches, and acting as an inline Layer 2 filter. For the second job, nftables has a bridge family for rules on frames that cross the bridge.
ip link add br0 type bridge
ip link set br0 type bridge stp_state 1 priority 61440
ip link set eth0 master br0
ip link set eth1 master br0
ip link set br0 up
ip link set eth0 up
ip link set eth1 upA bridge forwards frames without an IP address of its own. Without one, the gateway is not reachable on that network (OneUptime). Give management access to a separate interface, not to the bridged network.
Rules for the bridged design:
- One path only. If the two switches are also linked another way, such as a spare cable, a second gateway, or a VM host that bridges both networks, frames loop. Without STP the result is a broadcast storm. With STP, one of the paths is blocked (kernel docs).
- Control the STP root. With STP on, the bridge exchanges STP messages with both switches and could become the root bridge. Lower priority values win (NetworkManager reference), so give the gateway a high number, as in the example, and leave the root role to a switch.
- Switch port security. Each switch now sees the MAC addresses of the devices on the other side through one port. Port security or 802.1X on those ports can block that.
- VLANs. If the switches use trunks, the bridge must carry the same VLANs. Use a VLAN-aware bridge with trunk ports, as in Scenario E.
- Fail-closed. If the gateway stops, the link between the two networks stops with it. Plan for that.
| Routed gateway | Bridged gateway | |
|---|---|---|
| Broadcast domains | Two | One |
| Loop risk between the switches | None through the gateway | Yes, if a second path exists |
| What each switch sees | One host, the gateway’s MAC address | Learned MAC addresses from the other side |
| Filtering | IP rules (nftables inet, firewalld) |
Frame rules (nftables bridge) |
| Typical use | Gateway, NAT, security boundary | Inline Layer 2 filter, extending a segment |
Choosing a scenario
Start with Scenario B. Move to another one only when a specific requirement rules it out. The flow below shows the usual decision path, and the table compares the outcomes.
Read the flow from the top left. Most four-server setups end at Scenario B.
| Scenario | Layout | Internal servers visible from outside? | Upstream network must | Main risk |
|---|---|---|---|---|
| A: Layer 2 bridge | One network | Yes, directly | Hand out addresses and accept several MACs per port | No isolation at all |
| B: routing and NAT | Two networks | No, outbound only | Nothing extra | Gateway compromise, and NAT mistaken for a firewall |
| C: routing without NAT | Two networks | As far as your forward rules allow | Add a route to your subnet | A forward-rule mistake exposes a server |
| D: VMs and containers | Isolated bridge or bridged | Depends on NAT or bridged mode | Same as A or B | Hypervisor or Docker rules interfering with yours |
| E: VLAN-aware bridge | One network per VLAN | Per VLAN | Same as B or C | VLAN misconfiguration, ports left in VLAN 1 |
| F: gateway between two switches | Two networks (routed) or one (bridged) | Depends on routed or bridged mode | Same as A, B or C | Loops between the switches when bridged |
Making it permanent and checking it
The ip commands in the scenarios last until reboot. For a permanent setup, use whichever network tool your distribution already manages the interfaces with, and use only one of them per interface.
Ubuntu uses netplan by default, and CentOS-family systems use NetworkManager. Both are shown below, along with systemd-networkd.
Before you touch a live NIC
- Make sure you have console or out-of-band access. Moving an address onto a bridge can drop your own SSH session.
- Record the current state:
ip -br addr,ip route,ip -br link. - Add the bridge and move the address in one step, not over several logins.
- Prefer a method that can roll back. On Ubuntu,
netplan tryreverts the change if you do not confirm it.
Netplan (Ubuntu)
Scenario B. The uplink keeps its address, and the internal bridge holds eth1. Member interfaces must also be defined under ethernets, and STP settings go under parameters (OneUptime):
network:
version: 2
ethernets:
eth0:
addresses: [192.0.2.10/24]
routes:
- to: default
via: 192.0.2.1
nameservers:
addresses: [192.0.2.1]
eth1: {}
bridges:
br0:
interfaces: [eth1]
addresses: [10.10.10.1/24]
parameters:
stp: falseFor Scenario A, put both NICs in the bridge and move the uplink addressing onto it:
network:
version: 2
ethernets:
eth0: {}
eth1: {}
bridges:
br0:
interfaces: [eth0, eth1]
addresses: [192.0.2.10/24]
routes:
- to: default
via: 192.0.2.1
nameservers:
addresses: [192.0.2.1]Apply with netplan try, confirm, then check with bridge link show. Keep the file readable by root only: chmod 600.
Set STP on purpose, whichever tool you use. NetworkManager turns it on by default, and in Scenario A the bridge faces the upstream network, which will then see the bridge’s STP messages.
systemd-networkd
# 10-br0.netdev
[NetDev]
Name=br0
Kind=bridge
# 20-br0.network
[Match]
Name=br0
[Network]
Address=10.10.10.1/24
# 30-eth1.network
[Match]
Name=eth1
[Network]
Bridge=br0The uplink needs its own .network file. Enable forwarding with the sysctl.d file from Scenario B.
CentOS, RHEL and other Red Hat family systems (NetworkManager and firewalld)
NetworkManager manages bridges as connection profiles. The bridge settings and their defaults (NetworkManager reference) are:
| Setting | Default | Meaning |
|---|---|---|
stp |
TRUE | Spanning Tree Protocol on |
ageing-time |
300 | MAC ageing time, seconds |
forward-delay |
15 | STP forwarding delay, seconds |
priority |
32768 | STP priority, lowest becomes root |
vlan-filtering |
FALSE | VLAN filtering off |
CentOS Stream and its relatives (RHEL, AlmaLinux, Rocky Linux) use NetworkManager and firewalld by default, and the network-scripts package is not installed by default on version 9 (RackNerd). Use nmcli. These commands build the internal bridge of Scenario B: eth1 joins br0, which holds the internal address (OneUptime).
nmcli connection add type bridge con-name br0 ifname br0
nmcli connection modify br0 bridge.stp no
nmcli connection modify br0 ipv4.addresses 10.10.10.1/24
nmcli connection modify br0 ipv4.method manual
nmcli connection add type ethernet port-type bridge con-name br0-port1 ifname eth1 controller br0
nmcli connection up br0
nmcli connection up br0-port1
bridge link showCurrent releases name the port options port-type and controller. Older releases used slave-type and master. If nmcli rejects a command, check man nmcli on your system. For Scenario A, add the uplink NIC to the bridge the same way and move the uplink address onto br0. The Red Hat networking guide has the full chapter for each release.
For NAT, use firewalld instead of the nftables file from Scenario B, and do not run both. Put the uplink in the external zone, put the bridge in the internal zone, enable masquerade on the external zone, and add a policy that lets internal traffic out (OneUptime):
firewall-cmd --permanent --zone=external --change-interface=eth0
firewall-cmd --permanent --zone=internal --change-interface=br0
firewall-cmd --permanent --zone=external --add-masquerade
firewall-cmd --permanent --new-policy internal-to-external
firewall-cmd --permanent --policy internal-to-external --add-ingress-zone internal
firewall-cmd --permanent --policy internal-to-external --add-egress-zone external
firewall-cmd --permanent --policy internal-to-external --set-target ACCEPT
firewall-cmd --reloadThis policy accepts all internal traffic towards the external side. Narrow it to the services the servers need, as the best-practices checklist advises. Forwarding still needs the sysctl.d setting from Scenario B.
Verify
| Command | What it shows |
|---|---|
ip -d link show br0 |
Bridge details and options |
bridge link show |
Ports and their state, such as forwarding or blocking |
bridge fdb show br br0 |
MAC addresses the bridge has learned, and on which port |
bridge vlan show |
VLAN membership of each port |
ip route and ip neigh |
Routes and ARP entries |
sysctl net.ipv4.ip_forward |
1 means forwarding is on |
nft list ruleset |
The firewall and NAT rules in force |
tcpdump -ni eth0 |
What really leaves the uplink, including NAT results |
Security concerns
A gateway sits between networks of different trust, so it concentrates risk. The table lists the main concerns and what reduces each one.
| Concern | Why it matters | What to do |
|---|---|---|
| Merged Layer 2 domain (Scenario A) | Broadcasts, ARP, DHCP and discovery traffic cross between both sides. A rogue DHCP server on either side can answer the other. | Prefer routing. If you must bridge, limit what is bridged and filter with the nftables bridge family. |
| The IP firewall may not see bridged traffic | The br_netfilter module matters only when iptables must filter or NAT bridged traffic. ebtables and nftables work without it (kernel docs). The kernel docs call br_netfilter a legacy feature. |
Filter Layer 2 with nftables bridge rules. Test from a real client that your rules apply. |
| NAT is not a firewall | NAT hides addresses but does not block anything. A default-allow forward policy is an open door. | Set policy drop on input and forward. Allow only the flows you need. |
| The gateway is a high-value target | Whoever controls it sees all traffic crossing it and is one hop from every internal server. | Minimal packages, prompt patching, key-only SSH, management only from the internal side, logs sent off the box. |
| Anyone with root can capture everything | A bridge passes all traffic of its ports through one host. | Limit root access and encrypt internal traffic (TLS, SSH) so captures are useless. |
| Outbound abuse | A compromised internal server can call out to anywhere, including to an attacker’s control server. | Allow only needed destinations: DNS, time, update mirrors. Log what is denied. Send updates through a proxy where you can. |
| Spoofed MAC or IP addresses | A bridge learns whatever source MAC it sees and does not check it. | Strict reverse-path filtering (rp_filter = 1) on routed paths. Static addressing. Port security on the upstream switch. |
| Servers that should not talk to each other | Everything on one bridge can reach everything else on it. | Use the per-port isolated flag (bridge(8)), or separate VLANs with firewall rules between them. |
| STP and loop problems | With STP on, a bridge exchanges BPDUs with the upstream network. A loop without STP causes a broadcast storm. | Decide STP deliberately. Where no BPDUs are expected, set guard on on the port, so it is shut down if one arrives. |
| MAC table flooding | A flood of fake MAC addresses fills the FDB, and unknown unicast is then flooded to all ports. | On server ports with fixed hosts, consider turning learning and flood off and pinning MACs with static FDB entries. |
| The IPv6 side door | IPv4 NAT does not cover IPv6. The uplink may offer global IPv6 addresses or routed IPv6 paths. | Filter both protocols in one inet ruleset, or disable IPv6 on purpose. IPv6 forwarding has its own setting, net.ipv6.conf.all.forwarding. |
| Container runtimes rewrite the firewall | Docker sets the FORWARD policy to DROP when it enables forwarding, and by default allows any external source to reach published ports (Docker docs). |
Put rules in DOCKER-USER. Bind published ports to specific addresses. Review rules after every install. |
| Policy and compliance | Bridging can break the upstream network’s segmentation or port-security rules. | Get approval from the owner of the external network. Record the bridge in your network documentation. |
| Blind spots in logging | Bridged traffic does not appear in IP firewall logs. | Log forwarded flows. Keep time synchronised so logs line up. |
A good test is to assume one internal server is compromised and ask what it can reach. If the answer is “anything on the internet and every other server”, tighten the rules.
Best practices
Design
Bridge configuration
Firewall and NAT
Hardening and operations
Troubleshooting
| Symptom | Likely cause | Check and fix |
|---|---|---|
| SSH session drops when you add a NIC to the bridge | The IP address is still on the NIC, which no longer handles IP traffic | Move the address to br0. Work from a console. |
| Servers reach the gateway but not the outside | Forwarding is off | sysctl net.ipv4.ip_forward must show 1 |
| Forwarding is on, but nothing passes | The FORWARD policy is DROP and no rule allows the flow |
nft list ruleset, or iptables -L FORWARD -n -v |
| Ping to an address works, hostnames fail | No reachable DNS resolver | Check /etc/resolv.conf on the server and the forward rule for DNS |
| Packets leave but replies never arrive (Scenario C) | Upstream router has no route back to the internal subnet | Add the route, or use NAT |
| Replies dropped on asymmetric paths | Strict rp_filter discards them |
sysctl net.ipv4.conf.all.rp_filter. Loose mode (2) is a middle option. |
| Servers get no address or no connectivity (Scenario A) | Upstream port security or 802.1X blocks extra MACs | bridge fdb show br br0 shows the MACs the bridge sees. Check the upstream port. |
| High CPU and a network storm | A loop in the topology | bridge link show for blocking states. Enable STP or remove the second path. |
| Wi-Fi uplink will not join the bridge | Managed-mode Wi-Fi cannot be bridged | Use Ethernet, or use Scenario B |
| Traffic stopped after installing Docker | Docker set the FORWARD policy to DROP |
Add allow rules to DOCKER-USER |
| DHCP address of the gateway changed after bridging | br0 has a different MAC than the NIC had |
Set a fixed MAC on the bridge |
| Works until reboot | Forwarding or firewall rules were not persisted | sysctl.d file and the nftables service |
Limitations and gotchas
- Wi-Fi. A managed-mode Wi-Fi interface cannot normally be a bridge port. Only four-address mode, set up on both sides, allows it (kernel commit, libvirt wiki).
- Layer 2 only. A bridge does not route, does not translate addresses and cannot make a private subnet reachable from outside.
- STP is classic. Linux bridges do not support RSTP (Red Hat Developer), so failover after a link change is slower than with RSTP.
- Software forwarding. Bridging and NAT run on the gateway’s CPU, so its throughput depends on the hardware.
- NAT limits. The connection-tracking table has finite capacity. Inbound access needs extra rules. Masquerade needs kernel 3.18 or later (nftables wiki).
- Single point of failure. If the gateway is down, the other servers lose their way out. One uplink also means shared bandwidth.
- Tool conflicts. Two tools managing the same interface will overwrite each other’s settings.
- br_netfilter is legacy. The kernel docs discourage it and suggest nftables (kernel docs).
- VLAN defaults. VLAN filtering is off by default, and new ports start in VLAN 1 once it is on.
- Upstream consent. Bridging changes what the external network sees. Port security, 802.1X or policy there can block it.
References
- Linux kernel documentation: Ethernet bridging
- bridge(8) manual page, iproute2
- Introduction to Linux bridging commands and features (Red Hat Developer)
- NetworkManager reference: bridge settings
- Red Hat Enterprise Linux 8: Configuring a network bridge
- nftables wiki: Performing Network Address Translation (NAT)
- Docker documentation: Docker with iptables
- libvirt wiki: Networking
- Linux kernel commit: cfg80211, disallow bridging managed and ad-hoc interfaces
- OneUptime: How to configure a bridge with Netplan
- OneUptime: How to create a network bridge on Linux using iproute2
- OneUptime: How to configure IP forwarding and enable routing on Ubuntu
- OneUptime: How to configure a network bridge with nmcli on RHEL
- OneUptime: How to configure RHEL as a router with IP forwarding and NAT
- RackNerd: Installing network-scripts on AlmaLinux 9, Rocky Linux 9 and CentOS Stream 9
The systemd-networkd files, netplan try, the VLAN self and default-VLAN details, the nftables inet ruleset, the older NetworkManager option names (slave-type, master) and the STP priority value in Scenario F follow standard documented usage but were not checked line by line against the pages above. Test them on a lab machine first.