Debian 11, KVM and br0: replacing an unstable NIC with a USB Ethernet 2.5G adapter

Scenario

On a Debian GNU/Linux 11 Bullseye server running kernel 5.10.0-38-amd64, I had a fairly standard KVM/QEMU network setup:

eno1 -> br0 -> VM

The server was using a static address on the 192.168.50.0/24 LAN:

  • server: 192.168.50.23
  • gateway/router: 192.168.50.254
  • local Pi-hole DNS: 192.168.50.10

The bridge used by the virtual machines was br0. The old physical NIC was eno1, handled by the e1000e driver.

The symptom was easy to notice: the network would drop and then come back. Since eno1 was the physical port inside the bridge, every reset affected both the host and the VMs.

The kernel log

The first useful check was to look at kernel messages related to the NIC:

sudo dmesg -T | egrep -i "e1000e|tx unit hang|watchdog|reset|hang|netdev"

The relevant output looked like this:

[Sun Feb 22 21:01:27 2026] e1000e 0000:00:1f.6 eno1: Detected Hardware Unit Hang:
[Sun Feb 22 21:01:29 2026] e1000e 0000:00:1f.6 eno1: Detected Hardware Unit Hang:
[Sun Feb 22 21:01:31 2026] e1000e 0000:00:1f.6 eno1: Detected Hardware Unit Hang:
[Sun Feb 22 21:01:32 2026] NETDEV WATCHDOG: eno1 (e1000e): transmit queue 0 timed out
[Sun Feb 22 21:01:32 2026] WARNING: CPU: 10 PID: 0 at net/sched/sch_generic.c:470 dev_watchdog+0x260/0x270
[Sun Feb 22 21:01:32 2026] e1000e 0000:00:1f.6 eno1: Reset adapter unexpectedly
[Sun Feb 22 21:01:32 2026] br0: topology change detected, propagating
[Sun Feb 22 21:01:36 2026] e1000e 0000:00:1f.6 eno1: NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None

The important part is not br0. The sequence says something else:

  • Detected Hardware Unit Hang means the NIC, or the driver managing it, got stuck.
  • NETDEV WATCHDOG: transmit queue 0 timed out means the transmit queue timed out.
  • Reset adapter unexpectedly means the driver reset the NIC.
  • br0: topology change detected, propagating is a consequence: the bridge saw the physical port go down and come back up.

With bridge_stp on, the bridge propagates topology changes. That line was not the root cause. STP was reacting to the physical port reset.

So the issue was not KVM, not QEMU, and not br0. The issue was eno1, using the e1000e driver, hanging and being reset.

The same pattern appeared again over the following days:

[Mon Feb 23 07:58:10 2026] e1000e 0000:00:1f.6 eno1: Detected Hardware Unit Hang:
[Mon Feb 23 07:58:14 2026] e1000e 0000:00:1f.6 eno1: Reset adapter unexpectedly
[Mon Feb 23 07:58:14 2026] br0: topology change detected, propagating
[Tue Feb 24 08:49:30 2026] e1000e 0000:00:1f.6 eno1: Detected Hardware Unit Hang:
[Tue Feb 24 08:49:33 2026] e1000e 0000:00:1f.6 eno1: Reset adapter unexpectedly
[Tue Feb 24 08:49:34 2026] br0: topology change detected, propagating
[Sat Mar 14 14:11:26 2026] e1000e 0000:00:1f.6 eno1: Reset adapter unexpectedly
[Sat Mar 14 14:11:26 2026] br0: topology change detected, propagating

Original network configuration

The original /etc/network/interfaces file was:

source /etc/network/interfaces.d/*

auto lo
iface lo inet loopback

auto eno1
iface eno1 inet manual

auto br0
iface br0 inet static
    address 192.168.50.23
    network 192.168.50.0
    netmask 255.255.255.0
    broadcast 192.168.50.255
    gateway 192.168.50.254
    bridge_ports eno1
    bridge_stp on
    bridge_fd 0
    bridge_hello 2
    bridge_maxage 12
    bridge_maxwait 0

The key line was:

bridge_ports eno1

It means that eno1 was the physical port of the br0 bridge. When eno1 reset, the bridge lost and reacquired its physical path to the LAN. The VMs did not need changes: the weak point was underneath them, in the physical link.

Why the bridge was not the problem

On a KVM/QEMU host with a bridge, the VMs use br0 as if they were connected to the same LAN as the host.

The rule of thumb is:

correct:
physical NIC without IP -> br0 with IP -> VM

wrong:
physical NIC with IP + separate bridge

The physical NIC should not have its own IP address. It should only be a bridge port. The server IP stays on br0.

In this case, the goal was to replace only the physical port:

before:
eno1 -> br0 -> VM

after:
lanusb0 -> br0 -> VM

Detecting the new Realtek USB Ethernet 2.5G adapter

After plugging in the Realtek USB Ethernet 2.5G adapter, the kernel detected it like this:

usb 1-4: Product: USB 10/100/1G/2.5G LAN
usb 1-4: Manufacturer: Realtek
cdc_ncm 1-4:2.0: MAC-Address: 02:42:ac:32:00:25
cdc_ncm 1-4:2.0 enx0242ac320025: renamed from eth0

The initial interface name was:

enx0242ac320025

The MAC address was:

02:42:ac:32:00:25

With ip a, the adapter was up and had received a temporary DHCP address:

enx0242ac320025: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 192.168.50.84/24

That address was only useful for testing the link and cable. In the final configuration, the IP must be on br0, not on the physical NIC.

I also checked the link:

ethtool enx0242ac320025

Relevant output:

Link detected: yes

Giving the new adapter a stable name

The name enx0242ac320025 works, but it is not pleasant to read or maintain in a network config. I assigned a stable name based on the MAC address.

I created this file:

nano /etc/systemd/network/10-lanusb0.link

With this content:

[Match]
MACAddress=02:42:ac:32:00:25

[Link]
Name=lanusb0

This tells systemd/udev: when a NIC with MAC 02:42:ac:32:00:25 appears, name it lanusb0.

Before changing the network configuration, I checked that the link file was recognized:

udevadm test-builtin net_setup_link /sys/class/net/enx0242ac320025 2>/dev/null | grep -E 'Config file|ID_NET_NAME|lanusb0'

Output:

ID_NET_LINK_FILE=/etc/systemd/network/10-lanusb0.link

Back up the configuration

Before editing /etc/network/interfaces, I made a simple backup:

cp /etc/network/interfaces /etc/network/interfaces.bak

New network configuration

The new /etc/network/interfaces configuration replaces eno1 with lanusb0 as the bridge’s physical port:

source /etc/network/interfaces.d/*

auto lo
iface lo inet loopback

auto lanusb0
iface lanusb0 inet manual

auto br0
iface br0 inet static
    address 192.168.50.23
    network 192.168.50.0
    netmask 255.255.255.0
    broadcast 192.168.50.255
    gateway 192.168.50.254
    bridge_ports lanusb0
    bridge_stp on
    bridge_fd 0
    bridge_hello 2
    bridge_maxage 12
    bridge_maxwait 0

The important bits:

  • auto lo and iface lo inet loopback are the loopback interface and always stay there.
  • auto lanusb0 brings the new adapter up.
  • iface lanusb0 inet manual means the physical NIC does not get its own IP.
  • bridge_ports lanusb0 puts the new NIC inside br0.
  • the static IP stays on br0.
  • eno1 is removed because it should no longer be used.

Reboot

On a remote server, I would avoid doing something risky like:

ifdown br0 && ifup br0

If something goes wrong, SSH access can disappear.

The safer path was a reboot, keeping both network cables connected for the first boot:

reboot

After verifying that everything worked through lanusb0, I physically unplugged the cable from eno1 and left the old NIC as a possible fallback.

Verifying after reboot

After the reboot, I checked the interfaces and bridge:

ip a
brctl show
bridge link

Relevant output:

3: lanusb0: <BROADCAST,MULTICAST,UP,LOWER_UP> master br0 state UP
4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 192.168.50.23/24

And the bridge:

bridge name     bridge id               STP enabled     interfaces
br0             8000.0242ac320025       yes             lanusb0
                                                        vnet0
                                                        vnet1
                                                        vnet2

This confirms three important things:

  • lanusb0 is inside br0.
  • br0 still has the static IP 192.168.50.23.
  • vnet0, vnet1 and vnet2 are still inside br0.

The VMs continue to work without changes. Only the physical port used by the bridge changed.

DNS issue after the change

After changing cable and adapter, IP connectivity worked, but DNS did not.

Symptom:

ping google.com

Output:

ping: google.com: Name or service not known

/etc/resolv.conf contained:

# Generated by NetworkManager

The local DNS server is Pi-hole:

192.168.50.10

The router is:

192.168.50.254

Immediate fix, using only Pi-hole:

printf "nameserver 192.168.50.10\n" > /etc/resolv.conf

Or, with the router as fallback:

printf "nameserver 192.168.50.10\nnameserver 192.168.50.254\n" > /etc/resolv.conf

If everything should always go through Pi-hole, use only 192.168.50.10. If a fallback is preferred, add 192.168.50.254 too.

Inside the br0 block, you can add:

dns-nameservers 192.168.50.10

Or:

dns-nameservers 192.168.50.10 192.168.50.254

NetworkManager

I also checked NetworkManager:

nmcli device status
systemctl is-active NetworkManager

Output:

DEVICE   TYPE      STATE                   CONNECTION
br0      bridge    connected (externally)  br0
vnet0    tun       connected (externally)  vnet0
vnet1    tun       connected (externally)  vnet1
vnet2    tun       connected (externally)  vnet2
eno1     ethernet  unavailable             --
lanusb0  ethernet  unmanaged               --
lo       loopback  unmanaged               --

NetworkManager was active, but br0 showed as connected (externally). That means br0 is managed by /etc/network/interfaces.

lanusb0 being unmanaged is also correct: it should not be managed directly by NetworkManager, because it is only the physical bridge port.

Realtek USB 2.5G driver

The Realtek 2.5G adapter was detected as this USB device:

idVendor=0bda, idProduct=8156

On Debian 11 with kernel 5.10, it used cdc_ncm.

Module check:

lsmod | egrep 'r8152|cdc_ncm|cdc_ether'

Output:

cdc_ncm                53248  1 cdc_mbim
cdc_ether              24576  1 cdc_ncm
usbnet                 53248  3 cdc_mbim,cdc_ncm,cdc_ether

I also checked r8152:

modinfo r8152

The r8152 module existed, but it was old:

r8152 version v1.11.11

On Debian 11 with kernel 5.10, that module did not include the alias for 0bda:8156, so I did not force it.

The physical LAN is 1 Gbit, so cdc_ncm is fine for now. If real 2.5G throughput is needed later, or if stability issues appear, I would first consider a newer kernel or a Debian upgrade instead of installing external drivers.

Checks after the migration

To watch for resets, disconnects or carrier problems:

journalctl -k -b | grep -i -E 'lanusb0|cdc_ncm|usbnet|realtek|reset|disconnect|carrier|link'

Or:

dmesg -T | grep -i -E 'lanusb0|cdc_ncm|usbnet|realtek|reset|disconnect|carrier|link'

To follow the kernel log live:

journalctl -k -f | grep -i -E 'lanusb0|cdc_ncm|usbnet|realtek|reset|disconnect|link|carrier'

And to check interface counters:

ip -s link show lanusb0
ip -s link show br0

The counters to watch are mainly:

  • errors
  • dropped
  • overruns
  • carrier

If those counters stay clean and there are no new resets in the logs, the migration did its job.

Conclusion

The issue was caused by the integrated eno1 NIC, or by its e1000e driver.

The key errors were:

Detected Hardware Unit Hang
NETDEV WATCHDOG
Reset adapter unexpectedly

Since eno1 was the physical bridge port, each reset affected br0, and therefore both the host and the VMs.

The solution was to replace the physical bridge port with lanusb0, while leaving the IP address, bridge and VM configuration unchanged:

lanusb0 = physical port without IP
br0     = main interface with IP 192.168.50.23

In practice, the bridge was not “fixed”. The unstable port was removed from it.