Guides Networking
MTU, MSS and tunnels: why some sites load and others hang
Why tunnelled links break on big packets, how Path MTU Discovery fails, how to measure the real MTU with a ping, and the three ways to fix it.
Few network faults are as confusing as this one. The tunnel is up. You can ping across it. SSH connects, and then freezes the moment you list a large directory. A small web page loads and a big one stalls. Everything looks healthy, yet anything bulky hangs.
That pattern, where small packets work and large ones vanish, is the signature of an MTU problem. It is common on anything that wraps packets in extra headers, which is every tunnel and VPN there is. Once you know what to look for, it is one of the easiest problems to fix.
The three numbers
MTU is the maximum transmission unit: the largest IP packet an interface will send in one piece. Ethernet’s is 1500 bytes.
MSS is the maximum segment size: the largest chunk of TCP data a host will put in one packet. It is the MTU minus the IP and TCP headers, so 1460 for IPv4 on a 1500 byte link (1500 − 20 − 20) and 1440 for IPv6 (1500 − 40 − 20). Each side of a TCP connection announces its MSS when the connection opens.
Overhead is what a tunnel adds. Wrapping a packet in WireGuard over IPv4 adds an outer IP header (20 bytes), a UDP header (8) and WireGuard’s own header and tag (32): 60 bytes in all. The wrapped packet must still fit in the 1500 bytes the real link allows, so the packet inside the tunnel must be no bigger than 1440.
You can work this out for any combination with the MTU and MSS calculator. The point is that the inside of a tunnel has a smaller MTU than the outside.
What goes wrong
Your computer does not know the tunnel exists. If it believes the path allows 1500 byte packets, it will send them. The tunnel then has a packet that is too big to wrap and still fit, and it has two choices: fragment it, or drop it and say so.
Most modern traffic sets the Don’t Fragment bit, so the packet cannot be fragmented. The device that cannot forward it is supposed to drop it and send back an ICMP message saying “fragmentation needed”, along with the size that would fit. The sender then lowers its packet size and retries. This is Path MTU Discovery, and when it works nobody notices.
It fails when those ICMP messages never arrive. Firewalls that drop all ICMP “for security” are the usual culprit. The sender never learns why its big packets disappear, and the connection hangs. This is called a PMTUD black hole, and it explains the symptoms exactly. Small packets, such as the handshake and short requests, fit. Large ones, such as bulk data, do not.
IPv6 depends on this even more. Routers never fragment IPv6 packets, and ICMPv6 “Packet Too Big” messages are essential to the protocol. Blocking them breaks IPv6 outright.
Measuring the real MTU
Do not guess. Ask the path. A ping with the Don’t Fragment bit set and a chosen size tells you whether that size fits. The payload size is the MTU minus 28 bytes (20 for the IP header, 8 for ICMP).
For a 1500 byte MTU, the largest payload is 1472:
ping -M do -s 1472 -c 3 10.8.0.1
If it replies, 1500 fits. If it reports message too long or gets no answer, try smaller. Rather than guess by hand, let a loop search for the largest working size:
host=10.8.0.1
lo=1200
hi=1472
while [ "$lo" -lt "$hi" ]; do
mid=$(( (lo + hi + 1) / 2 ))
if ping -M do -s "$mid" -c 1 -W 1 "$host" >/dev/null 2>&1; then
lo=$mid
else
hi=$(( mid - 1 ))
fi
done
echo "Largest payload: $lo bytes, path MTU: $(( lo + 28 ))"
Run it more than once, since a lost reply can make a good size look bad. Replace 10.8.0.1 with the far end of your tunnel.
On Windows the equivalent is ping -f -l 1472 -n 3 10.8.0.1. On macOS it is ping -D -s 1472 -c 3 10.8.0.1.
tracepath can also report the path MTU as it discovers it:
tracepath -n 10.8.0.1
And to see the MSS values that two hosts actually negotiate, capture a connection opening:
sudo tcpdump -ni eth0 -v 'tcp[tcpflags] & tcp-syn != 0'
Look for mss in the options of the SYN packets. If it is larger than the tunnel can carry, you have found the fault.
Three ways to fix it
1. Set the tunnel interface MTU correctly
The cleanest fix is to tell the tunnel its real MTU, so the host above it sends the right size from the start. For WireGuard, set it in the interface section of the config:
[Interface]
MTU = 1420
Or change it live:
sudo ip link set dev wg0 mtu 1420
WireGuard’s tooling usually picks 1420 for a 1500 byte link. That figure leaves room for an IPv6 outer header, which is why it is 1420 and not 1440, so it is safe and costs a few bytes on IPv4.
2. Clamp the MSS on the gateway
If you cannot change every host, rewrite the MSS in TCP handshakes as they pass through your router. Hosts then never send segments too large for the tunnel. With iptables:
sudo iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
sudo iptables -t mangle -A FORWARD -i wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
With nftables:
sudo nft add table inet mssfix
sudo nft add chain inet mssfix forward '{ type filter hook forward priority mangle; }'
sudo nft add rule inet mssfix forward oifname "wg0" tcp flags syn tcp option maxseg size set rt mtu
sudo nft add rule inet mssfix forward iifname "wg0" tcp flags syn tcp option maxseg size set rt mtu
MSS clamping only helps TCP. UDP traffic, such as DNS responses and video calls, is not covered, so it is a patch and not a cure. It is nonetheless the most widely used fix because it works on a router without touching any client.
3. Let ICMP through
Whatever else you do, stop dropping the ICMP messages Path MTU Discovery needs. For IPv4, allow ICMP type 3, code 4 (“fragmentation needed”). For IPv6, allow ICMPv6 “Packet Too Big” (type 2). Blocking them turns a self-healing problem into a mystery.
Tunnels that stack
Each layer adds its own overhead, and the sum comes out of the original link. WireGuard over a PPPoE link (MTU 1492) is 1492 − 60 = 1432 on IPv4. An IPsec tunnel carried over WireGuard adds its worst-case overhead on top. The calculator handles stacks, and it also warns when the result falls below 1280, the smallest MTU IPv6 permits.
Worth knowing: ESP in IPsec pads each packet, so its true overhead varies. The calculator uses the worst case, which is safe but may leave a few bytes unused.
Where Cloudflare Tunnel fits
A Cloudflare Tunnel for published applications does not have this problem in the same way, because cloudflared proxies at the application layer, such as HTTP, SSH or TCP. It terminates each connection and opens a new one, so there is no inner packet size to get wrong. MTU matters again if you use private network routing with WARP, which does carry IP packets through a tunnel.
A checklist for “big things hang”
- Confirm small works and large fails: ping with a small payload, then a large one with
-M do. - Find the largest size that works, using the loop above.
- Compare it with what the calculator predicts for your stack.
- Set the interface MTU, and clamp the MSS on the gateway if you have clients you cannot change.
- Check that ICMP “fragmentation needed” and “Packet Too Big” can pass your firewalls.
- Retest with the same big transfer that failed.
Sources: the IP and TCP header sizes and Path MTU Discovery behaviour are defined in RFC 791, RFC 793, RFC 1191 and RFC 8201. WireGuard’s message format is described in the WireGuard protocol documentation.