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 (
r8169driver), interfaceeno1 - Wi-Fi: interface
wlp13s0 - Router: ISP gateway at
192.168.1.254, both NICs on the same192.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 oneno1while the routing table had nothing for it. Test without-I, in the state that fails.nmcli device reapplymade it worse. Reapplying removed the IPv4 address outright and did not restart DHCP.nmcli con up <profile>brought it back.ip monitor route addressin 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 -4andcurl -6separately. - Background
sudoand tcpdump don’t mix. Runningsudo tcpdump ... & ping ...just stops the job while sudo waits for a password. Runsudo -vfirst 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
| Problem | Symptom | Cause | Fix |
|---|---|---|---|
| DNS | Names don’t resolve over the cable | Static DNS pointing at a box that no longer exists, with ignore-auto-dns on | Clear the static DNS, re-enable automatic DNS |
| Routing | Network is unreachable with Wi-Fi off | ARP flux: the Wi-Fi NIC answered ARP for the wired IP, NetworkManager saw a conflict and the wired routes disappeared | arp_ignore=1, arp_announce=2 in /etc/sysctl.d/ |
| Speed | Link stuck at 100 Mb/s | An old ethtool ... advertise 0x020f limiting the NIC to 10/100 | Re-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.