WireGuard MTU Calculator and Fragmentation Fix
Work out the right WireGuard MTU from your path MTU, understand the 60 and 80 byte overhead, measure the real ceiling, and clamp TCP MSS correctly.
The tunnel comes up. wg show prints a recent handshake. Ping works, SSH works, DNS works. Then a web page loads halfway and hangs, apt update freezes at 0%, and a file copy stalls at a few kilobytes. Nothing in the logs. That pattern is almost never a key problem or a firewall problem. It is MTU, and the arithmetic behind it is small enough to do on paper.
If you want the number without the reading, the mesh topology designer on this site computes a WireGuard MTU for the common transports alongside the peer configuration it generates. The rest of this article explains where that number comes from, how to measure your own path instead of trusting a default, and what to do when you cannot set an MTU on the client at all.
Where the bytes go
Every packet that enters a WireGuard interface comes out the other side wrapped in four layers. The overhead is fixed, which is what makes the calculation reliable.
| Layer | Bytes | Notes |
|---|---|---|
| WireGuard transport header | 16 | message type, reserved bytes, receiver index, 64-bit counter |
| Poly1305 authentication tag | 16 | appended to every encrypted payload |
| UDP header | 8 | source port, destination port, length, checksum |
| Outer IPv4 header | 20 | no options in normal use |
| Outer IPv6 header | 40 | fixed length, no options in normal use |
Total added to each inner packet: 60 bytes over IPv4, 80 bytes over IPv6. There is no negotiation and no variable-length option block, because the protocol fixes its cipher suite rather than negotiating one. That is the whole reason the number is stable enough to precompute.
The calculator
Take the MTU of the path the encrypted UDP actually travels over, subtract 80, and use that as the WireGuard interface MTU. Subtracting 80 rather than 60 costs 20 bytes of payload and buys immunity to the day your ISP turns on IPv6 and the outer header grows.
| Underlying path | Path MTU | WireGuard MTU | IPv4 TCP MSS |
|---|---|---|---|
| Ethernet or fibre, standard | 1500 | 1420 | 1380 |
| PPPoE (DSL, some fibre) | 1492 | 1412 | 1372 |
| DS-Lite, 6rd, generic IP-in-IP | 1480 | 1400 | 1360 |
| GRE or some cloud overlays | 1460 | 1380 | 1340 |
| Constrained mobile path | 1430 | 1350 | 1310 |
| Conservative universal floor | 1360 | 1280 | 1240 |
TCP MSS is the WireGuard MTU minus 40 for IPv4 inside the tunnel (20 bytes of IP header plus 20 of TCP header), or minus 60 for IPv6 inside.
Two limits are worth remembering. RFC 8200 requires every link carrying IPv6 to support a 1280 byte MTU, so if IPv6 runs inside the tunnel, do not configure the WireGuard interface below 1280. And a tunnel MTU of 1280 is the value to reach for when a path refuses to be measured: it is almost always safe, and the cost is roughly a tenth of your per-packet payload against 1420, not a multiple.
Why wg-quick usually lands on 1420
wg-quick does not hardcode a value. When MTU is absent from the [Interface] block, it looks up the route to the peer endpoint, falls back to the system default route if there is no endpoint to look up, and subtracts the worst case 80 bytes from whatever MTU it finds. On an ordinary 1500 byte Ethernet path that produces 1420, which is where the familiar number comes from. On a 1492 byte PPPoE line it produces 1412 automatically.
That automatic discovery fails in exactly one situation, and it is a common one: when the local link claims 1500 but something between you and the peer carries less. The kernel has no idea a mid-path device is smaller, so it hands wg-quick 1500, wg-quick hands you 1420, and every packet near full size disappears silently.
Measure the path instead of guessing
Do this from the machine that terminates the tunnel, against the peer’s public endpoint address, with the tunnel down or at least with the traffic routed outside it. The do-not-fragment flag is what makes the test meaningful.
ping -M do -s 1472 vpn.example.net
ICMP adds 28 bytes on IPv4, 8 for the ICMP header and 20 for the IP header, so a payload of 1472 is a 1500 byte packet on the wire. If it replies, your path is a clean 1500. If it returns Message too long or simply times out while smaller pings succeed, step down and repeat:
for s in 1472 1452 1432 1412 1392 1372 1352 1332; do
ping -M do -c1 -W2 -s "$s" vpn.example.net >/dev/null 2>&1 \
&& { echo "largest payload that passes: $s -> path MTU $((s+28))"; break; }
done
Add 28 to the largest payload that passes to get the path MTU, then subtract 80 for the WireGuard MTU. A path MTU of 1452 gives a payload of 1424 and a WireGuard MTU of 1372.
Once the tunnel is up with that value, confirm from inside it. The target is the far end’s tunnel address, and the payload to test is the interface MTU minus 28:
ping -M do -c3 -s 1392 10.7.0.1
A 1420 byte interface should pass 1392 and fail 1393. If it fails well below that, the interface MTU is still too high for the path.
Set it on both ends
MTU is a property of an interface, not of a session. Each end fragments or refuses to fragment based on its own configured value, so a low path affects traffic in both directions and both ends need the lower number.
[Interface]
PrivateKey = <private key>
Address = 10.7.0.1/24
ListenPort = 51820
MTU = 1412
Change it live without editing files by setting it on the link, which is useful while bisecting:
sudo ip link set mtu 1412 dev wg0
That reverts on the next wg-quick down and up cycle, so write the working value into the config once you have it.
Clamping TCP MSS, and why it is only half a fix
On a router that terminates the tunnel for a whole LAN you cannot reach into every client to set an MTU. The standard answer is to rewrite the MSS option in TCP SYN packets crossing the tunnel so both endpoints agree to send smaller segments.
With iptables:
sudo iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
With nftables:
table inet mangle {
chain forward {
type filter hook forward priority mangle;
oifname "wg0" tcp flags syn tcp option maxseg size set rt mtu
}
}
Both are worth having. Neither is complete, for a specific reason: MSS clamping only affects TCP. QUIC, which now carries a large share of web traffic through HTTP/3, runs over UDP and never sees an MSS option. Neither do DNS over UDP, most VPN-inside-VPN traffic, or media streams over UDP. If the interface MTU is wrong, clamping hides the problem for HTTPS over TCP while UDP applications keep stalling in ways that look unrelated.
The order that works: set a correct interface MTU first, then clamp MSS as a safety net for clients you do not control.
Why path MTU discovery does not rescue you
Classic path MTU discovery depends on routers returning ICMP “fragmentation needed” (IPv4) or “packet too big” (IPv6) messages. Large numbers of networks filter inbound ICMP wholesale, so the sender never learns the path is smaller and simply keeps retransmitting packets that are silently dropped. RFC 4821 documents this failure mode as an ICMP black hole and specifies packetization layer path MTU discovery, which probes with real payload data instead of relying on ICMP, as the fix. Linux implements it and the behaviour is controlled by net.ipv4.tcp_mtu_probing:
sudo sysctl -w net.ipv4.tcp_mtu_probing=1
A value of 1 enables probing only after a suspected black hole is detected, which is the safe setting. Treat it as a mitigation for paths you do not control, not a substitute for a correct MTU on your own interface.
Two related traps are worth naming. If your own firewall drops all ICMP inbound, you have created the black hole yourself, so permit ICMP type 3 code 4 on IPv4 and ICMPv6 type 2 at minimum. And if containers run behind the tunnel, the container bridge MTU must be no larger than the tunnel MTU; a Docker bridge sitting at 1500 behind a 1412 tunnel reproduces the exact stall this article is about, one layer further in.
A diagnostic order for the stall
wg showreports a recent handshake, so the crypto and the endpoint are fine.- Small pings pass through the tunnel, so routing and
AllowedIPsare fine. If they do not, the problem is upstream of MTU; start with the fundamentals of keys, AllowedIPs, and NAT traversal. ping -M do -s 1392inside the tunnel fails while-s 500passes. That is the confirmation.- Lower the interface MTU on both ends until the large ping passes, or measure the underlay first with the loop above and compute the value directly.
- Clamp MSS on any router that forwards for clients you cannot configure.
- Re-test with something UDP based, not only a browser, so a clamp does not mask a still-wrong MTU.
When the MTU is right and it still stalls
A handful of causes imitate an MTU problem closely enough to waste an afternoon. Offload features are the usual suspect: generic receive offload and segmentation offload on the physical NIC can hand oversized segments to a tunnel that cannot carry them, and disabling them with ethtool -K eth0 gro off gso off tso off is a quick way to rule it in or out. A second tunnel in the path, such as WireGuard inside a corporate VPN or over a mobile hotspot that itself tunnels, means subtracting overhead twice. And a NAT mapping expiring mid-transfer produces a stall that looks identical for the first few seconds, which is why PersistentKeepalive = 25 belongs on the NAT’d side before you start bisecting MTU values.
If you are still building the tunnel rather than repairing one, the step by step Ubuntu server and client setup walks through the configuration with a sane MTU already in place, and the comparison of WireGuard against Tailscale covers what an overlay does about MTU on your behalf.
See also
Sources
Related
WireGuard vs Tailscale: Which One to Run
Tailscale runs on WireGuard, so the real choice is about the control plane: key distribution, NAT traversal, relays, identity, and what fails without them.
WireGuard Setup on Ubuntu: Server and Client
A config-first WireGuard install on Ubuntu: key generation, wg0.conf for server and client, forwarding, firewall rules, and adding peers without downtime.
WireGuard Explained: Keys, AllowedIPs, and NAT Traversal
How WireGuard really works: the static key handshake, what AllowedIPs controls, PersistentKeepalive and NAT, MTU math, and how it compares to OpenVPN.