← cd ../blog
hero image — bench photo of the build

Wired Ethernet 'connected' but no internet without Wi-Fi (and stuck at 100 Mb/s)

My desktop said the cable was connected, yet the moment I turned Wi-Fi off the internet was gone. Three separate problems were stacked on top of each other: a dead DNS server, ARP flux between two NICs on the same subnet, and a forgotten ethtool command capping the link at 100 Mb/s.


My desktop has both a wired connection and Wi-Fi. The settings panel showed the Ethernet cable as connected, but as soon as I switched Wi-Fi off, nothing loaded. Turn Wi-Fi back on and everything worked again. So the cable was “working” in the sense that nobody was actually using it.

It turned out to be three unrelated problems layered on top of each other, each one hiding the next. This post walks through how each was found, with the commands, so you can run the same checks on your own machine.

All addresses, MACs and names below are placeholders. Swap in your own when you run the commands.

Environment

  • OS: Ubuntu 26.04, kernel 7.0, managed by NetworkManager (netplan renderer)
  • Wired NIC: Realtek RTL8125 2.5GbE (r8169 driver), interface eno1
  • Wi-Fi: interface wlp13s0
  • Router: ISP gateway at 192.168.1.254, both NICs on the same 192.168.1.0/24
  • ISP plan: 1 Gb/s symmetric
  • Docker installed (lots of br-* / veth* interfaces, not relevant here)

Step 1: get a picture of every interface

Start with addresses, routes and DNS in one go:

ip -br link
ip -br addr
ip route
nmcli -t device status
resolvectl status

With both links up, the routing table looked healthy. The wired link even had the better (lower) metric, so it should already have been the preferred path:

default via 192.168.1.254 dev eno1    proto dhcp src 192.168.1.10 metric 100
default via 192.168.1.254 dev wlp13s0 proto dhcp src 192.168.1.11 metric 600

resolvectl status was where the first thing stood out. Each link had its own DNS server, and they were different:

Link 2 (eno1)
    Current DNS Server: 192.168.1.53
Link 3 (wlp13s0)
    Current DNS Server: 192.168.1.254

The wired link was not using the router for DNS.

Problem 1: a hard-coded DNS server that no longer exists

Checking the wired profile:

nmcli -f ipv4.dns,ipv4.ignore-auto-dns,ipv4.method con show netplan-eno1
ipv4.dns:                192.168.1.53
ipv4.ignore-auto-dns:    yes
ipv4.method:             auto

At some point I had pointed this connection at a local DNS box (think Pi-hole) and told it to ignore whatever DNS the router hands out. That box was long gone. To confirm, test reachability and an actual lookup against each server:

ping -c2 -W2 -I eno1 1.1.1.1                       # raw IP over the cable
ping -c2 -W2 192.168.1.53                          # the configured DNS box
dig +time=2 +tries=1 @192.168.1.53  example.com +short
dig +time=2 +tries=1 @192.168.1.254 example.com +short

The ping to 1.1.1.1 over eno1 worked, the DNS box was Destination Host Unreachable, and only the router answered the dig. That explained the original symptom: with Wi-Fi on, name resolution quietly went through the Wi-Fi link’s DNS. With Wi-Fi off, nothing could resolve a hostname, which looks exactly like “no internet”.

Fix: remove the static DNS and accept the router’s again. You can do it in the GUI (clear the DNS field and turn “Automatic” back on), or:

sudo nmcli con mod netplan-eno1 ipv4.dns "" ipv4.ignore-auto-dns no
sudo nmcli con up netplan-eno1
resolvectl dns eno1                                # should now show the router
resolvectl query --interface=eno1 example.com      # should resolve

Lookups over the cable worked. I turned Wi-Fi off, and it still failed.

Problem 2: no route at all once Wi-Fi is off

This time I ran the checks with Wi-Fi switched off. That is the state that actually fails, so it is the one worth looking at:

nmcli -t device status | head -3
ip route | grep default
resolvectl dns
ping -c2 -W2 1.1.1.1
ping -c2 -W2 example.com
curl -4 -sS -o /dev/null -w 'v4 %{http_code}\n' -m 8 https://example.com
curl -6 -sS -o /dev/null -w 'v6 %{http_code}\n' -m 8 https://example.com
eno1:ethernet:connected:netplan-eno1
ping: connect: Network is unreachable
ping: example.com: Temporary failure in name resolution

ip route | grep default printed nothing. The link was up and NetworkManager called it “connected”, but there was no default route through it. Network is unreachable is the kernel saying it has no idea where to send the packet.

Back with Wi-Fi on, looking at the IPv4 side of eno1 specifically:

ip -4 addr show eno1
ip -4 route
ip -4 route show table all | grep eno1
ip rule
inet 192.168.1.10/24 brd 192.168.1.255 scope global dynamic noprefixroute eno1

default via 192.168.1.254 dev wlp13s0 proto dhcp src 192.168.1.11 metric 600
192.168.1.0/24 dev wlp13s0 proto kernel scope link src 192.168.1.11 metric 600

eno1 had its address, but none of its routes were there: no default route, and not even the 192.168.1.0/24 subnet route. There was no policy routing (ip rule showed only the three default tables) and no VPN client running. The routes were just gone. Meanwhile the NetworkManager log claimed the opposite:

journalctl -u NetworkManager --since "-30 min" --no-pager | grep -E 'eno1|wlp13s0'
policy: set 'netplan-eno1' (eno1) as default for IPv4 routing and DNS

And right next to it, the clue that solved it:

device (eno1): conflict detected for IP address 192.168.1.10 with host aa:bb:cc:00:00:02

aa:bb:cc:00:00:02 was not some other device on the network. It was my own Wi-Fi card. Earlier in the log there was the mirror image: the wired card’s MAC “conflicting” with the Wi-Fi card’s IP.

Root cause: ARP flux

When a Linux machine has two NICs on the same subnet, the default kernel settings allow either NIC to answer an ARP “who has 192.168.1.10?” request for any address the host owns. That is net.ipv4.conf.all.arp_ignore = 0 and arp_announce = 0. It’s called ARP flux.

So when NetworkManager runs address conflict detection on eno1’s IP, the Wi-Fi card helpfully replies “that’s me”. NetworkManager sees a second MAC claiming the address, flags a conflict, and the wired link ends up with its address but without its routes. While Wi-Fi is up, Wi-Fi’s routes carry all traffic and nobody notices. Turn Wi-Fi off and the cable has nowhere to send anything.

Check your current values with:

sysctl net.ipv4.conf.all.arp_ignore net.ipv4.conf.all.arp_announce

Fix: make each interface answer ARP only for its own address, and persist it:

printf 'net.ipv4.conf.all.arp_ignore = 1\nnet.ipv4.conf.all.arp_announce = 2\n' \
  | sudo tee /etc/sysctl.d/60-multihome-arp.conf
sudo sysctl --system
nmcli con up netplan-eno1
ip -4 route | grep 192.168.1
  • arp_ignore = 1: only reply if the requested IP is configured on the interface the request arrived on.
  • arp_announce = 2: when sending ARP, always use the best local address for that interface as the source.

Afterwards both subnet routes exist and the wired default route is back:

default via 192.168.1.254 dev eno1    proto dhcp src 192.168.1.10 metric 100
default via 192.168.1.254 dev wlp13s0 proto dhcp src 192.168.1.11 metric 600
192.168.1.0/24 dev eno1    proto kernel scope link src 192.168.1.10 metric 100
192.168.1.0/24 dev wlp13s0 proto kernel scope link src 192.168.1.11 metric 600

Right after reconnecting you may briefly see the wired default route at metric 20100 instead of 100. NetworkManager adds that penalty while its connectivity check is still pending, and it drops back to 100 a few seconds later. Confirm with:

nmcli -f GENERAL.IP4-CONNECTIVITY dev show eno1     # want: 4 (full)
nmcli -f CONNECTIVITY general                      # want: full

With that in place, turning Wi-Fi off finally left the machine online over the cable.

Things that misled me along the way

  • ping -I eno1 <router> succeeding proves very little. Binding a socket to a device lets the kernel ARP for an on-link neighbour even when the interface has no routes. A tcpdump showed perfect echo replies on eno1 while the routing table had nothing for it. Test without -I, in the state that fails.
  • nmcli device reapply made it worse. Reapplying removed the IPv4 address outright and did not restart DHCP. nmcli con up <profile> brought it back. ip monitor route address in a second terminal is handy for watching exactly what gets added and removed while you poke at it.
  • IPv6 kept things half-working. The router also hands out IPv6, so some sites loaded over v6 while v4 was broken. That makes “it works sometimes” more confusing than it needs to be. Test with curl -4 and curl -6 separately.
  • Background sudo and tcpdump don’t mix. Running sudo tcpdump ... & ping ... just stops the job while sudo waits for a password. Run sudo -v first so the credential is cached.

Problem 3: the cable works, but only at 100 Mb/s

With wired working, a speed test topped out around 94 Mb/s on a 1 Gb/s plan, and the settings panel said 100 Mb/s. ethtool shows both what the card can do and what it is offering to the switch:

ethtool eno1
ethtool -i eno1
Supported link modes:   10baseT/Half 10baseT/Full
                        100baseT/Half 100baseT/Full
                        1000baseT/Full
                        2500baseT/Full
Advertised link modes:  10baseT/Half 10baseT/Full
                        100baseT/Half 100baseT/Full
Link partner advertised link modes:  100baseT/Half 100baseT/Full
                                     1000baseT/Half 1000baseT/Full
                                     2500baseT/Full
                                     5000baseT/Full
Speed: 100Mb/s

The router offered up to 5 Gb/s and the NIC supported 2.5 Gb/s, but the NIC was only advertising 10 and 100 Mb/s, so auto-negotiation settled on 100. The computer was limiting itself.

Nothing in /etc/systemd/network, udev rules or NetworkManager dispatcher scripts set this, and the NetworkManager profile left speed on auto (802-3-ethernet.speed: 0). The culprit was in my shell history:

grep ethtool ~/.bash_history
sudo ethtool --set-eee eno1 eee off
sudo ethtool -s eno1 autoneg on advertise 0x020f

At some point I had restricted the advertisement to the 10/100 modes, most likely to stop link drops (Realtek 2.5G NICs and marginal cables are well known for flapping at gigabit), and then forgotten about it. The kernel log showed the link going down and up repeatedly earlier, all at 100 Mb/s:

journalctl -k --since "-7 days" --no-pager | grep -iE 'eno1|r8169' | grep -i link

Fix: advertise every mode again (10/100/1000 plus 2500baseT):

sudo ethtool -s eno1 autoneg on advertise 0x80000000002f
sleep 5
ethtool eno1 | grep -E 'Speed|Link detected'

Then watch for flapping for a minute:

journalctl -kf | grep eno1

No Link is Down / Link is Up loop this time, and the speed test came in at roughly 926 Mb/s down and 926 Mb/s up, which is what a gigabit line actually delivers after overhead.

If it had flapped, the next suspect is the cable. 100 Mb/s only uses two of the four pairs, while gigabit needs all four, so a cable with one bad pair works fine at 100 and falls over at 1000. A known-good Cat5e/Cat6 cable and a different router port are the cheap things to try first.

Because a manual ethtool -s only lasts until reboot or replug, I rebooted to confirm nothing reapplied the old limit:

ethtool eno1 | grep Speed               # want 1000Mb/s or higher
ip -4 route | grep default              # eno1 at metric 100

Both held.

Summary

ProblemSymptomCauseFix
DNSNames don’t resolve over the cableStatic DNS pointing at a box that no longer exists, with ignore-auto-dns onClear the static DNS, re-enable automatic DNS
RoutingNetwork is unreachable with Wi-Fi offARP flux: the Wi-Fi NIC answered ARP for the wired IP, NetworkManager saw a conflict and the wired routes disappearedarp_ignore=1, arp_announce=2 in /etc/sysctl.d/
SpeedLink stuck at 100 Mb/sAn old ethtool ... advertise 0x020f limiting the NIC to 10/100Re-advertise all modes with ethtool

The general lesson: when a machine has two NICs on the same subnet, “it works with Wi-Fi on” tells you almost nothing about the cable. Always reproduce the failing state, with the other link off, before trusting any test.