Server

Understanding MTU and Path MTU Discovery

By Prabath Thalangama· October 24, 2026· 3 min read
#networking#tcp#troubleshooting

Introduction

A connection that completes its handshake and then freezes on the first real data — a large HTTP response, an SSH banner, a git clone — is almost always an MTU problem. It's counter-intuitive because small packets (ping, the handshake) work fine. This post explains why and how to fix it.

Concepts

  • MTU (Maximum Transmission Unit) — the largest IP packet an interface will send in one piece. Standard Ethernet is 1500 bytes. PPPoE knocks it to 1492, a typical IPsec/WireGuard tunnel to ~1400, GRE to 1476.
  • MSS (Maximum Segment Size) — the largest chunk of TCP payload per packet, advertised by each side in the SYN. It's MTU - 40 (IPv4: 20-byte IP header + 20-byte TCP header), so 1460 on standard Ethernet.
  • Path MTU — the smallest MTU along the entire path. A packet bigger than this must be fragmented or dropped.

Path MTU Discovery (PMTUD)

IPv4 hosts set the Don't Fragment (DF) bit and rely on routers to send back an ICMP "fragmentation needed" (type 3, code 4) message when a packet is too big. The sender then lowers its packet size and retries.

PMTUD breaks when that ICMP message is filtered — which many firewalls do, blanket-blocking all ICMP. Now the oversized packet is silently dropped, the sender never learns, and it keeps retransmitting the same too-big packet forever. That's the hang. This is called a PMTUD black hole.

IPv6 has no fragmentation by routers at all — PMTUD is mandatory, and blocking ICMPv6 "Packet Too Big" breaks IPv6 entirely.

Hands-on

Find the path MTU

ping with DF set and a payload size, binary-searching:

# Linux: -M do sets DF; -s is payload (add 28 for the ICMP+IP headers)
ping -M do -s 1472 example.com     # 1472 + 28 = 1500, standard Ethernet
ping -M do -s 1400 example.com     # if 1472 fails, try smaller

# The largest -s that doesn't say "Message too long" or hang,
# plus 28, is your path MTU.

macOS: ping -D -s 1472 example.com. Windows: ping -f -l 1472 example.com.

tracepath example.com (Linux) does the search automatically and prints the MTU at each hop.

Fix 1: MSS clamping (on the router/gateway)

Rewrite the MSS in passing SYN packets to match the path:

# iptables (nftables equivalent: 'tcp option maxseg size set rt mtu')
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
  -j TCPMSS --clamp-mss-to-pmtu

This is the right fix on a NAT gateway / VPN concentrator — it doesn't touch endpoints. Nginx has proxy_pass equivalents; OpenVPN has mssfix; strongSwan has mss in the config.

Fix 2: Lower the interface MTU (on the endpoint or tunnel)

ip link set dev wg0 mtu 1380
# persist it in the interface config (netplan, NetworkManager, ifcfg)

Use this when you control the tunnel interface and can't clamp on a middlebox.

Fix 3 (least good): allow the ICMP

If you own the firewall, don't blanket-drop ICMP:

iptables -A INPUT  -p icmp --icmp-type fragmentation-needed -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT

Verification and troubleshooting

  • Small requests work, big ones hang — textbook PMTUD black hole. Do the ping -M do search; the failing size tells you the path MTU.
  • git clone / apt update stalls at a percentage — same thing; the large data packets can't get through.
  • Works over IPv4, IPv6 totally broken — ICMPv6 "Packet Too Big" is being filtered somewhere. This is not optional for IPv6.
  • Fixed with MTU 1400, still occasional stalls — the path MTU is even lower (double tunnel, some mobile networks are ~1358). Lower it more or clamp.
  • After a WireGuard/IPsec setup, everything is slow/broken — set the tunnel MTU explicitly; the defaults are often too optimistic.
  • tcpdump shows repeated identical large packets from your host — the sender never got the ICMP and is retransmitting blindly.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.