Bonding Modes: Which One to Use

The kernel bonding driver exposes seven modes numbered 0 through 6. Choosing the wrong one against your switch configuration is the most common source of bonding failures.

Mode 0 (balance-rr) does round-robin transmission across all slaves. It requires switch-side configuration - specifically, all ports must be in the same EtherChannel group. Without that, you get out-of-order packets and TCP retransmits. We tested this on a pair of Cisco 9300s and saw 12% TCP retransmit rate with mode 0 and no LACP configured on the switch side.

Mode 1 (active-backup) is the safe default for environments where you control only the Linux host. One slave is active, others are standby. Failover time is controlled by miimon and the arp_interval settings. On our test server with miimon=100, failover completed in under 200ms every time.

Mode 4 (802.3ad) is full LACP. Both sides negotiate link aggregation. This gives you both redundancy and bandwidth aggregation. It requires LACP enabled on the switch port group. This is what you want on any server with dual 10GbE or 25GbE ports connected to a managed switch.

Mode 5 (balance-tlb) and Mode 6 (balance-alb) work without switch cooperation by adapting transmit load balancing to current load. Mode 6 includes receive load balancing via ARP manipulation. These are useful when you cannot touch the switch config but want some load distribution.

For bare-metal servers in a datacenter: use mode 4 with LACP if the switch supports it. For VMs or cloud instances where the hypervisor controls the uplink: use mode 1. For home labs or environments with unmanaged switches: use mode 1 or mode 5.

# Check available bonding modes in kernel docs
modinfo bonding | grep -A2 'mode:'

Kernel Module Parameters

The bonding module loads with defaults that are inadequate for production. The two most important parameters are miimon and arp_interval - only use one of them, not both.

miimon sets the MII link monitoring frequency in milliseconds. The kernel polls each slave NIC's MII status register. A value of 100ms is reasonable. Lower than 50ms causes unnecessary CPU overhead. The default is 0, which means monitoring is disabled - a bond with no monitoring will not detect a failed NIC.

arp_interval enables ARP-based monitoring instead of MII. This is useful when MII reports link-up even though the switch port is misconfigured or a fiber transceiver is failing. Set arp_ip_target to a reachable IP on your network. We use the gateway address.

arp_validate controls whether the module validates ARP replies from slaves or just sends ARP probes. Values are none (0), active (1), backup (2), or all (3). For mode 1, setting arp_validate=all caught a bad cable that MII monitoring missed entirely.

fail_over_mac is important in environments where MAC address cloning causes issues (some switches or hypervisors). Setting fail_over_mac=active makes the bond interface use the active slave's MAC address rather than a fixed one.

# Load bonding with specific parameters at boot
cat /etc/modprobe.d/bonding.conf
options bonding mode=4 miimon=100 lacp_rate=fast xmit_hash_policy=layer3+4

Configuration on RHEL 9 and Rocky Linux 9 with nmcli

RHEL 9 and its downstreams use NetworkManager as the primary network configuration tool. Direct edits to ifcfg files still work but are deprecated as of RHEL 9. Use nmcli for reproducible, version-controlled configurations.

The sequence is: create the bond master connection, then add each slave (now called 'port' in NM terminology) to it, then bring the bond up. The bond master connection defines the mode and monitoring parameters. Each port connection references the master by name.

After creating the bond, verify the slave connections are actually attached before activating. A common mistake is activating the master before the slaves are configured, leaving the bond with no ports.

For LACP (mode 4), set both lacp-rate and xmit-hash-policy. The default hash policy is layer2, which uses only source and destination MAC addresses - this concentrates traffic if most of your flows are to a single MAC. layer3+4 hashes on IP and port tuples, giving better distribution.

Check bond status with cat /proc/net/bonding/bond0. This file shows the current mode, active slave, MII status of each slave, and LACP state if applicable. It is the ground truth for bond health.

# Create bond master
nmcli con add type bond con-name bond0 ifname bond0 \
  bond.options "mode=4,miimon=100,lacp_rate=fast,xmit_hash_policy=layer3+4"

# Add first slave (adjust ifname for your hardware)
nmcli con add type ethernet con-name bond0-port1 ifname enp3s0 master bond0

# Add second slave
nmcli con add type ethernet con-name bond0-port2 ifname enp4s0 master bond0

# Set IP on bond master
nmcli con mod bond0 ipv4.addresses 10.0.1.50/24 ipv4.gateway 10.0.1.1 \
  ipv4.dns "10.0.1.1 8.8.8.8" ipv4.method manual

# Bring it up
nmcli con up bond0

# Verify
cat /proc/net/bonding/bond0
// advertisement

Configuration on Ubuntu 24.04 with systemd-networkd

Ubuntu 24.04 ships with both NetworkManager and systemd-networkd. For server installs without a GUI, systemd-networkd is cleaner and easier to template. Disable NetworkManager first if you go this route to avoid conflicts.

systemd-networkd uses three file types: .netdev for virtual device creation, .network for address and routing, and .link for device naming. For bonding, you create a .netdev file defining the bond and its options, then .network files for each physical NIC binding them to the bond, then a .network file for the bond interface itself with the IP configuration.

File naming matters. systemd-networkd processes files in lexicographic order. Prefix your bond files with a number lower than your physical NIC files so the bond device exists before the NICs try to join it. We use 10- for the bond and 20- for the slaves.

Restart systemd-networkd after any config change and use networkctl status to verify state. A properly configured bond will show 'routable' state with both slaves listed as 'enslaved'.

For systems doing automated provisioning, these .netdev and .network files are easily templated with Ansible or any configuration management tool. Teams using AI-assisted DevOps pipelines on platforms like taskbotshub.ai can generate these templates dynamically based on host hardware profiles detected at provisioning time.

# /etc/systemd/network/10-bond0.netdev
[NetDev]
Name=bond0
Kind=bond

[Bond]
Mode=802.3ad
TransmitHashPolicy=layer3+4
MIIMonitorSec=100ms
LACPTransmitRate=fast

---

# /etc/systemd/network/20-enp3s0.network
[Match]
Name=enp3s0

[Network]
Bond=bond0

---

# /etc/systemd/network/20-enp4s0.network
[Match]
Name=enp4s0

[Network]
Bond=bond0

---

# /etc/systemd/network/10-bond0.network
[Match]
Name=bond0

[Network]
Address=10.0.1.50/24
Gateway=10.0.1.1
DNS=10.0.1.1

---

# Apply
systemctl restart systemd-networkd
networkctl status bond0

Configuration on Debian 12 with /etc/network/interfaces

Debian 12 still supports the classic /etc/network/interfaces file via ifupdown2. The ifenslave package must be installed for bonding support. This approach is predictable and requires no daemon management.

The interfaces file syntax for bonding uses bond- prefixed options. These map directly to module parameters. One advantage of this method is that the entire network configuration is a single readable text file that you can diff, version, and review in a pull request.

After editing the interfaces file, bring the bond up with ifup bond0. Check the result with ip link show bond0 and cat /proc/net/bonding/bond0.

Note that ifupdown2 in Debian 12 supports the bond-slaves directive but the preferred term in newer documentation is bond-ports. Both work. Stick to one for consistency.

# Install required package
apt install ifenslave

# /etc/network/interfaces
auto bond0
iface bond0 inet static
    address 10.0.1.50
    netmask 255.255.255.0
    gateway 10.0.1.1
    dns-nameservers 10.0.1.1
    bond-slaves enp3s0 enp4s0
    bond-mode 802.3ad
    bond-miimon 100
    bond-lacp-rate fast
    bond-xmit-hash-policy layer3+4

auto enp3s0
iface enp3s0 inet manual
    bond-master bond0

auto enp4s0
iface enp4s0 inet manual
    bond-master bond0

# Apply
ifup bond0
cat /proc/net/bonding/bond0

Testing Failover and Measuring Failover Time

Do not trust your bond configuration until you have tested failover under real conditions. We have seen configurations that looked correct in /proc/net/bonding but failed silently when a NIC was pulled because miimon was set to 0.

The cleanest test is to run a continuous ping from another host while physically unplugging one NIC or using ip link set to simulate failure. Measure the gap in ping responses. With miimon=100, expect 100-300ms of dropped packets. With miimon=1000 (a common misconfiguration), failover takes 1-3 seconds.

For a more realistic load test, use iperf3 while inducing failover. Run iperf3 server on a remote host, start a long transfer, then pull a cable. Count the retransmits reported by iperf3 on the client side.

To simulate NIC failure without physical access:

Always test both directions: remove the active slave, then restore it and verify it rejoins the bond. Then remove a backup slave and verify the bond continues operating. The bond should log these events to the kernel ring buffer - check with dmesg | grep bond.

If you run automated infrastructure, a failover test can be integrated into a CI pipeline. Tools like taskbotshub.ai support triggering infrastructure validation scripts on a schedule, which lets you run weekly bond health checks across a fleet without manual intervention.

# Simulate active slave failure
ip link set enp3s0 down

# Watch bond status
watch -n0.5 cat /proc/net/bonding/bond0

# Check kernel events
dmesg | grep -i bond | tail -20

# Measure failover with ping timestamps
ping -i 0.2 10.0.1.1 | awk '{ print strftime("%T"), $0; fflush() }'

# iperf3 test during failover
# On remote host:
iperf3 -s
# On bond host:
iperf3 -c 10.0.1.100 -t 60 --logfile /tmp/iperf_bond_test.txt
// advertisement

Switch-Side Configuration for 802.3ad LACP

Linux-side LACP configuration is only half the picture. The switch ports must be configured as an LACP port channel or the bond will fall back to single-link operation. The bond will still come up and pass traffic on one link, but you lose both redundancy and aggregation.

On Cisco IOS/IOS-XE, configure the switch ports as a port-channel with LACP active mode. The Linux bond should be set to lacp_rate=fast to send LACPDUs every second rather than every 30 seconds. This speeds up LACP negotiation and failure detection.

On Arista EOS and Juniper EX-series, the configuration follows similar patterns. The key parameters to match between the Linux bond and the switch are: LACP rate (fast/slow), port-channel mode (active/passive), and hashing algorithm. Mismatched hash policies do not cause failure but do cause uneven load distribution.

After configuring both sides, verify LACP negotiation with cat /proc/net/bonding/bond0 and look for the '802.3ad info' section. It shows the LACP actor and partner key, port state, and whether LACP is running. If the partner key shows 0x0000, the switch is not responding to LACP.

For Cisco specifically:

The port-channel load-balance command on the switch should match the Linux xmit_hash_policy. Use src-dst-ip on the switch to complement layer3+4 on the Linux side.

! Cisco IOS-XE port-channel config for two ports
interface GigabitEthernet1/0/1
  channel-group 1 mode active
  spanning-tree portfast trunk
!
interface GigabitEthernet1/0/2
  channel-group 1 mode active
  spanning-tree portfast trunk
!
interface Port-channel1
  description bond0-server01
  switchport mode trunk
  switchport trunk allowed vlan 10,20,30
!
port-channel load-balance src-dst-ip

Bonding Over VLANs and Bridging

A bond interface can serve as the parent for VLAN sub-interfaces or as a bridge member. This is common in hypervisor hosts where the bond carries multiple VLANs for different VM networks.

For VLAN sub-interfaces on a bond, create the bond first, then add 802.1q sub-interfaces on top. Each VLAN gets its own interface (bond0.10, bond0.20, etc.) with independent IP addresses or as bridge members.

For KVM/libvirt hosts, the typical pattern is bond0 as the physical aggregation layer, then a Linux bridge (br0) on top of bond0, with the bridge being the interface that libvirt or QEMU binds VMs to. This gives VMs direct access to the physical network segment while the bond handles redundancy transparently.

One pitfall: when using a bridge on top of a bond, STP (Spanning Tree Protocol) on the bridge can cause a 30-second delay when the bond interface comes up, blocking traffic until STP converges. Disable STP on the Linux bridge if the upstream switch already handles loop prevention, or use RSTP (Rapid STP).

For containers and namespaces, the bond interface can be moved into a network namespace using ip link set bond0 netns, but this is rarely done. More commonly, you create a veth pair with one end in the container namespace and the other attached to a bridge that sits on the bond.

# VLAN sub-interfaces on bond0
ip link add link bond0 name bond0.10 type vlan id 10
ip link add link bond0 name bond0.20 type vlan id 20
ip addr add 10.10.0.1/24 dev bond0.10
ip addr add 10.20.0.1/24 dev bond0.20
ip link set bond0.10 up
ip link set bond0.20 up

# Bridge on top of bond for KVM
ip link add br0 type bridge
ip link set bond0 master br0
ip link set br0 up
bridge link show

Monitoring Bond Health in Production

Relying on /proc/net/bonding/bond0 manually is not production monitoring. You need alerting when a slave goes down, even if the bond itself is still operational on the remaining link.

The simplest approach is a script that parses /proc/net/bonding/bond0 and counts active slaves. If the count drops below the expected number, trigger an alert. Run this from cron every minute or as a systemd timer.

For environments with Prometheus and node_exporter, the bonding collector is included by default as of node_exporter 1.7.0. It exports metrics including node_bonding_slaves (total slaves), node_bonding_active (currently active slaves), and per-slave link state. Alert on node_bonding_active < node_bonding_slaves.

For SNMP-based monitoring, the Linux bonding driver does not expose SNMP OIDs directly. You need a custom SNMP extend or an agent wrapper.

Ethtool provides additional signal. ethtool enp3s0 shows NIC-level link speed, duplex, and PHY status. A slave that reports 100Mbps when it should be 10Gbps indicates a cable or transceiver fault before the bond detects total failure.

Set up syslog forwarding for kernel messages. Bond state changes generate kernel log messages at KERN_INFO level. Forwarding these to a central syslog or SIEM gives you an audit trail of all failover events with timestamps.

#!/bin/bash
# /usr/local/bin/check_bond.sh
BOND=bond0
EXPECTED_SLAVES=2

ACTIVE=$(grep -c 'MII Status: up' /proc/net/bonding/$BOND 2>/dev/null || echo 0)

if [ "$ACTIVE" -lt "$EXPECTED_SLAVES" ]; then
  echo "CRITICAL: $BOND has only $ACTIVE/$EXPECTED_SLAVES slaves active"
  exit 2
fi
echo "OK: $BOND $ACTIVE/$EXPECTED_SLAVES slaves active"
exit 0

# node_exporter Prometheus query for alerting:
# alert: BondDegradedAlert
# expr: node_bonding_active < node_bonding_slaves
# for: 1m
// advertisement

Common Problems and Fixes

The most frequent issue we see is a bond that appears healthy in /proc/net/bonding but has asymmetric throughput - all traffic running over one slave. This is almost always an LACP negotiation failure. Check partner key in the 802.3ad info section. If it reads 0, the switch is not sending LACPDUs back. Confirm the switch port-channel is in active mode, not passive - two passive ends will never negotiate.

ARP flux is a classic problem with bonded and multi-homed Linux hosts. When the bond has an IP and one or more slaves also have IPs, or when you have multiple bonds, the kernel may respond to ARP requests on the wrong interface. Set arp_filter=1 and rp_filter=1 per interface, or use arp_announce and arp_ignore sysctl values.

Duplicate MAC address conflicts occur when two bond interfaces are configured without specifying a unique MAC, or when a VM migration brings up a bond with the same MAC on a different segment. Use ip link set bond0 address to assign a specific MAC.

The 'bonding: bond0: Warning: No 802.3ad response from the link partner' message in dmesg means LACP is not receiving responses. This is almost always a switch configuration problem, not a Linux problem.

NIC driver bugs affect bonding. Some drivers report MII link-up even with a disconnected cable. If you suspect this, switch to arp_interval monitoring. We had this problem with an older i40e driver version on Intel X710 cards - updating to the vendor-provided driver from 2.22.18 to 2.23.17 fixed it.

# Fix ARP flux on bonded hosts
sysctl -w net.ipv4.conf.all.arp_filter=1
sysctl -w net.ipv4.conf.bond0.arp_ignore=1
sysctl -w net.ipv4.conf.bond0.arp_announce=2

# Make permanent
cat >> /etc/sysctl.d/99-bond-arp.conf << EOF
net.ipv4.conf.all.arp_filter = 1
net.ipv4.conf.bond0.arp_ignore = 1
net.ipv4.conf.bond0.arp_announce = 2
EOF

# Check LACP negotiation detail
cat /proc/net/bonding/bond0 | grep -A10 '802.3ad info'

# Force re-read of NIC driver version
ethtool -i enp3s0 | grep -E 'driver|version'