Skip to main content
RunBook Academy

← All break/fix scenarios in Linux

advancedNetworking~40 min

Break/Fix: small requests succeed, large ones hang, and ping says the network is fine

Reported symptoms

  • A health endpoint returning 200 bytes works perfectly; downloading a 5 MB artefact hangs part way and eventually times out
  • SSH connects and prints the banner, then freezes the first time a command produces more than a screenful of output
  • `ping` to the same host reports 0% loss with normal latency
  • Monitoring is green throughout, because every check is small
  • Hosts on the same VLAN are unaffected; only traffic that crosses the router misbehaves
  • The transfer always stalls at roughly the same number of bytes

Evidence

  • · `ping -c4 198.51.100.20` reports 0% packet loss
  • · `ping -M do -s 1472 198.51.100.20` succeeds
  • · `ping -M do -s 8972 198.51.100.20` reports 100% packet loss with no local error
  • · `ping -M do -s 8972 192.0.2.30` to a same-VLAN host succeeds
  • · `ip link show eth0` reports `mtu 9000`
  • · `tracepath 198.51.100.20` reports `pmtu 1500` at the first router hop
  • · `ss -ti` on the stalled socket shows a large `mss` and a `retrans` counter climbing while `bytes_acked` stops moving
  • · `sudo tcpdump -ni eth0 host 198.51.100.20` shows the same large segment retransmitted with exponential backoff and no ICMP arriving
Diagnosis and resolutionclick to reveal

Root cause

The interface is configured for jumbo frames at 9000 bytes and the storage VLAN supports it, but the path to hosts on the far side of the router includes a hop limited to 1500. Any packet larger than 1500 bytes is discarded there. TCP is supposed to learn this: the router sends an ICMP destination-unreachable with the fragmentation-needed code, carrying the next-hop MTU, and the sender lowers its segment size. That message is being dropped by a firewall rule that blocks ICMP wholesale, so Path MTU Discovery is blackholed — the sender never learns anything, keeps retransmitting a segment that can never be delivered, and stalls. Small exchanges fit inside 1500 bytes and are entirely unaffected, which is why the handshake, the banner, the DNS lookup and every monitoring check succeed. The failure is a function of payload size, not of host, service or time, and nothing in the application or TLS layer has any visibility into it.

Remediation

Measure the real path MTU with `ping -M do -s` before changing anything — the size that succeeds plus 28 is the answer. Restore service immediately by bringing the interface MTU down to the smallest MTU on the path with `sudo ip link set eth0 mtu 1500`, and persist it in netplan or NetworkManager rather than leaving it as a runtime value. Then fix the two real defects. First, stop filtering ICMP destination-unreachable: a rule that blocks all ICMP converts every MTU mismatch anywhere on the path into a silent hang, and the same rule breaks IPv6 outright because IPv6 routers cannot fragment at all. Second, either make the path consistently jumbo end to end or accept 1500 everywhere; a jumbo MTU that is correct on the hosts and wrong on one hop is worse than no jumbo at all. Where the path crosses a boundary you do not control, clamp MSS on the router so TCP never offers a segment the path cannot carry.

Verification

`ping -M do -s N` must succeed at the configured MTU minus 28 to every peer the interface is expected to reach, and this is the check that can fail — run it against a far-side host, not only a same-VLAN one. `tracepath` must report a consistent pmtu with no reduction partway. Transfer something large enough to matter under a time bound, for example `curl -sS --max-time 30 -o /dev/null -w "%{size_download}" https://svc.example.com/artifact.tar`, and confirm the full size arrives rather than a partial one. During that transfer `ss -ti` must show `retrans` static. Confirm PMTUD works rather than assuming it: `ip route get 198.51.100.20` should show a cached lower MTU appearing after a large transfer once the ICMP is unblocked. Finally reboot or re-apply the network configuration and re-run the ping test.

Prevention

Treat MTU as a property of the whole path rather than of an interface. A jumbo deployment is only real if every switch, every inter-switch link, every router interface, every tunnel and every VM host along the path agrees, and the only proof is a large do-not-fragment probe end to end. Never block ICMP as a class: permit destination-unreachable at minimum, and permit ICMPv6 packet-too-big unconditionally, because IPv6 has no router fragmentation and the protocol is not optional. Clamp MSS to the path MTU at L3 boundaries and on tunnel endpoints. Add a large-packet probe to monitoring alongside the small health checks, since a check that fits in one frame cannot detect this class of fault at all.

Reported symptoms

  • The build agents cannot pull artefacts from the artefact server. curl hangs after a few hundred kilobytes and eventually times out.
  • The artefact server’s /healthz endpoint, which returns 200 bytes, responds instantly from the same agents.
  • SSH from an agent to the artefact server connects, prints the banner, gives a prompt — and then freezes the first time a command produces a page or more of output. ls on a small directory is fine. ls -lR is not.
  • ping between them is clean: 0% loss, sub-millisecond latency.
  • Monitoring has been green throughout, on both hosts.
  • Agents on the same VLAN as the artefact server work perfectly. Agents in the other rack do not.
  • Someone noticed the transfer always stalls around the same offset.

Evidence provided

$ ping -c4 198.51.100.20
PING 198.51.100.20 (198.51.100.20) 56(84) bytes of data.
64 bytes from 198.51.100.20: icmp_seq=1 ttl=63 time=0.412 ms
64 bytes from 198.51.100.20: icmp_seq=4 ttl=63 time=0.398 ms
--- 198.51.100.20 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3050ms

$ ping -M do -c2 -s 1472 198.51.100.20
1480 bytes from 198.51.100.20: icmp_seq=1 ttl=63 time=0.451 ms
--- 198.51.100.20 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss

$ ping -M do -c2 -s 8972 198.51.100.20
--- 198.51.100.20 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1013ms

$ ping -M do -c2 -s 8972 192.0.2.30
8980 bytes from 192.0.2.30: icmp_seq=1 ttl=64 time=0.702 ms
--- 192.0.2.30 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss

$ ip link show eth0
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc mq state UP mode DEFAULT
    link/ether 52:54:00:9a:0b:1c brd ff:ff:ff:ff:ff:ff

$ tracepath -n 198.51.100.20
 1?: [LOCALHOST]                      pmtu 9000
 1:  192.0.2.1                         0.380ms
 1:  192.0.2.1                         0.291ms
 2:  192.0.2.1                         0.302ms pmtu 1500
 2:  no reply
 3:  no reply

$ ss -ti dst 198.51.100.20 | tail -2
	 cubic wscale:7,7 rto:1920 rtt:0.44/0.2 mss:8948 pmtu:9000
	 bytes_sent:1449216 bytes_acked:262144 retrans:0/47 lastsnd:8112

$ sudo tcpdump -ni eth0 host 198.51.100.20 and not icmp | tail -4
09:41:02.118 IP 192.0.2.44.51882 > 198.51.100.20.443: Flags [.], seq 262145:271093, length 8948
09:41:03.142 IP 192.0.2.44.51882 > 198.51.100.20.443: Flags [.], seq 262145:271093, length 8948
09:41:05.190 IP 192.0.2.44.51882 > 198.51.100.20.443: Flags [.], seq 262145:271093, length 8948
09:41:09.286 IP 192.0.2.44.51882 > 198.51.100.20.443: Flags [.], seq 262145:271093, length 8948

$ sudo tcpdump -ni eth0 icmp -c 5 -w /dev/null
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C0 packets captured

Work the evidence before reading on

The transcript contains the answer twice over, and the second occurrence is the more important one:

  1. ping succeeds and ping -M do -s 8972 to the same address fails completely. Work out what those two commands differ in — there are two differences, and both matter.
  2. The 8972-byte probe to the same-VLAN host succeeds. So the local interface can send a 9000-byte frame.
  3. tracepath reports pmtu 1500 at hop 2.
  4. tcpdump shows the same segment, same sequence numbers, sent four times with doubling gaps. bytes_acked has stopped at 262144 while bytes_sent keeps rising.
  5. The ICMP capture recorded nothing at all.

Before continuing: what is TCP supposed to receive that would tell it to send a smaller segment, and what does an empty ICMP capture imply about it?

Root cause

1. ping and ping -M do -s ask different questions

A default ping sends 56 bytes of payload — an 84-byte packet — with fragmentation permitted. It will traverse almost any path. A clean ping result means the addresses are reachable and the routing is sane. It says nothing whatsoever about how large a packet the path can carry, which is why “ping is fine” is one of the least useful facts in a troubleshooting session.

ping -M do -s 8972 sets the Don’t Fragment bit and sends a 9000-byte packet (8972 payload + 8 ICMP header + 20 IP header). That is the actual question.

The two failure modes look different and the difference is diagnostic:

  • Local error (ping: local error: message too long, mtu=1500) — your own interface refused it. The problem is on this host.
  • 100% loss with no error — the packet left, and something on the path silently discarded it. The problem is out there.

Here it is the second, which points at the path.

2. Path MTU Discovery is supposed to fix this automatically

The mechanism exists precisely for a path with a smaller link in the middle:

  1. TCP sends a segment with the Don’t Fragment bit set (Linux sets it by default).
  2. The router whose next hop cannot carry it drops the packet and sends back ICMP type 3, code 4 — destination unreachable, fragmentation needed — including the MTU it can carry.
  3. The sender caches a lower MTU for that destination and retransmits in smaller segments.

Step 3 never happens if step 2 never arrives.

3. The ICMP is being filtered

The capture on icmp recorded nothing during an active stall. A site firewall rule blocks ICMP as a class — almost always added years ago as “hardening”, and almost always without anyone considering which ICMP types the network depends on to function.

With that message filtered, the sender’s model of the path is simply wrong and nothing can correct it. ss -ti shows the consequence plainly: mss:8948 and pmtu:9000, both of which are false, and a retrans counter climbing while bytes_acked stands still. TCP is behaving exactly as designed — retransmit, back off, retransmit — against a segment that will never be delivered no matter how many times it is sent.

This is a PMTUD black hole, and the name is apt: packets go in, no information comes out.

4. Why size is the axis and everything else is noise

Anything that fits inside 1500 bytes works perfectly:

  • The TCP handshake, and usually the TLS handshake.
  • The SSH banner and key exchange, and small commands.
  • DNS.
  • A 200-byte health check — hence three weeks of green monitoring.

Anything that fills the window stalls at the first full-size segment. The transfer always dies at roughly the same offset because that offset is where the sender’s congestion window first grows past a single under-1500-byte segment.

Same-VLAN traffic is fine because it never reaches the router. So the fault appears to be about which rack, when it is really about which size, and the rack correlation sends the investigation straight to the network team’s routing configuration.

Resolution

  1. Measure the path MTU rather than assuming it. Binary-search with the do-not-fragment bit; the largest size that succeeds plus 28 is the path MTU:
  2. `` # Substitute your own values before running: PEER=198.51.100.20 for s in 1472 2972 4972 6972 8972; do printf '%s: ' "$s" ping -M do -c1 -W1 -s "$s" "$PEER" >/dev/null 2>&1 && echo ok || echo fail done ``
  3. Confirm where it drops. tracepath -n "$PEER" names the hop that reduces the pmtu, which is the hop to take to the network team
  4. Restore service now by matching the interface to the path. This is a workaround, and it works immediately:
  5. `` sudo ip link set eth0 mtu 1500 ``
  6. Re-test the large transfer before doing anything else, to confirm the hypothesis was right
  7. Persist the MTU. A runtime ip link set is gone at the next reboot or netplan apply. On netplan:
  8. `` network: ethernets: eth0: mtu: 1500 ``
  9. Fix the ICMP filtering, which is the defect that turned a configuration mismatch into a silent hang. At minimum permit destination-unreachable inbound:
  10. `` sudo nft add rule inet filter input icmp type destination-unreachable accept sudo nft add rule inet filter input icmpv6 type packet-too-big accept ``
  11. Take the same finding to whoever owns the boundary firewall. A host-level rule does not help if the message is dropped two hops away, and it usually is
  12. Decide what the MTU policy actually is. Either make the path jumbo end to end — every switch, inter-switch link, router interface and hypervisor bridge — or standardise on 1500. A jumbo configuration that is right on the hosts and wrong on one hop is worse than not having it
  13. Clamp MSS at the L3 boundary as a belt-and-braces measure, so TCP never offers a segment the path cannot carry even if PMTUD fails again:
  14. `` tcp flags syn tcp option maxseg size set rt mtu ``

Verification

  1. A full-size do-not-fragment probe succeeds to a far-side host. ping -M do -c2 -s 1472 198.51.100.20 at MTU 1500, or the jumbo equivalent if you fixed the path. Testing only a same-VLAN peer reproduces the original mistake
  2. tracepath is consistent end to end. tracepath -n 198.51.100.20 reports one pmtu with no reduction at an intermediate hop
  3. A large transfer completes under a time bound. This is the check that can fail and the one the users care about:
  4. `` curl -sS --max-time 30 -o /dev/null -w '%{size_download} bytes in %{time_total}s\n' https://svc.example.com/artifact.tar ``
  5. No retransmissions during that transfer. Run ss -ti dst 198.51.100.20 while it is in flight and confirm retrans stays at zero and bytes_acked tracks bytes_sent
  6. PMTUD is working again, not merely unneeded. After a large transfer, ip route get 198.51.100.20 should show a cached MTU learned from the path. A route with no cached MTU on a path that needs one means the ICMP is still not arriving
  7. ICMP actually arrives. Capture during a deliberately oversized transmission: sudo tcpdump -ni eth0 icmp should now show the fragmentation-needed message rather than nothing
  8. SSH behaves. Run ls -lR /usr over the SSH session that used to freeze. It is a crude test and it is the one the reporter will repeat
  9. It survives a reboot. Reboot or re-apply the network configuration and re-run the do-not-fragment probe. An MTU set only at runtime regresses silently
  10. Monitoring can now see this class of fault. Add a large-packet probe and confirm it goes red when you temporarily lower an MTU somewhere on the path

Prevention

  • Test MTU end to end, with ping -M do -s, as part of network acceptance. A jumbo rollout that has not been probed across every path it claims to cover has not been verified.
  • Never filter ICMP as a class. Permit destination-unreachable, and permit ICMPv6 packet-too-big and neighbour discovery unconditionally. Write the reason in a comment next to the rule so the next hardening pass does not remove it.
  • Clamp MSS to the path MTU at L3 boundaries and on every tunnel endpoint. It is a one-line rule that makes TCP correct even when PMTUD is broken by someone else’s firewall.
  • Include a large-payload check in monitoring. A 200-byte health probe is structurally incapable of detecting this, and the three weeks of green dashboards in this incident were not a monitoring gap so much as a monitoring design that only ever asked one question.
  • Document the MTU for every VLAN and every tunnel, and treat a change to it as a change to every host on it.
  • When a report contains the words “small requests work, large ones hang”, check MTU before anything else. That sentence has essentially one cause.