The symptom, and why your metrics stay green
Everything you test by hand works. curl pulls pages. wget is fine. Health checks pass, latency graphs are flat, the error rate is zero. Then a real browser loads a real site and it takes thirty seconds, or never finishes at all.
The metrics are clean because the failure happens before anything reaches the thing you're measuring. When a packet is too large for the path and the do-not-fragment bit is set, it gets dropped, and an ICMP "fragmentation needed" message is supposed to come back so the sender knows to shrink. That's Path MTU Discovery. In practice ICMP is filtered widely across the public internet and especially on mobile carriers, so the message never arrives. The sender learns nothing and retransmits a packet that will always be dropped.
The connection stalls rather than fails, and a stall is worse than an error, because nothing anywhere logs it. From your side the request simply never happened.
Why curl passes and the browser dies
Most tunnels ship with MSS clamping enabled. It rewrites an option in the TCP handshake so both ends agree on a smaller segment size before any data moves. It's a good fix and it works.
It also only works on TCP.
QUIC runs over UDP. There's no handshake option to rewrite, so the clamp does nothing. Chrome tries QUIC first against any server advertising HTTP/3, which is now most large sites. So the browser takes the one path your workaround doesn't cover, while every command line tool you reach for takes the path it does. That's the whole trap: the tool you debug with is the tool that can't reproduce the bug.
What the numbers look like
| Client MTU | Result | Note |
|---|---|---|
| 1420 | 0 of 6 parallel streams completed | WireGuard's default. Not slow, not degraded. Nothing finished. |
| 1360 | 33 Mbps | Same tunnel, same route, same endpoint. One number changed. |
1420 is WireGuard's default and it's a sensible one: 1500 bytes of Ethernet minus 80 bytes of WireGuard overhead. The assumption baked into it is that you have a full 1500 underneath. Over a mobile carrier, through PPPoE, or inside a second layer of encapsulation such as IPsec, you don't. Every extra layer takes its bytes off the top and the default stops fitting.
Finding your real number
Send progressively larger pings with fragmentation forbidden and watch for where the replies stop coming back.
# Linux and macOS. -s is the payload, so add 28 bytes
# for the IP and ICMP headers to get the real MTU.
ping -M do -s 1372 -c 3 1.1.1.1 # 1372 + 28 = 1400
ping -M do -s 1392 -c 3 1.1.1.1 # 1420
# Windows
ping -f -l 1372 1.1.1.1Walk the size down until replies return, add 28, and that's your path MTU. Then set the tunnel below it, because the tunnel's own overhead comes off that number too. If you'd rather not do arithmetic under pressure, 1360 is a boring value that survives most carrier paths.
Test the fix with something that speaks HTTP/3, not with the tool that was already passing. A green curl proves less than it looks like it proves.
The fixes, in order
- Set the client MTU explicitly. This is the real fix and the only one that covers UDP and QUIC.
- Keep MSS clamping anyway. It costs nothing and it catches TCP for clients whose MTU you don't control.
- Don't treat the clamp as the fix. If clamping is all you have, you've built a system that passes every test you run and fails for every user on Chrome.
- Never debug with a live customer config. Reproduce on a config you own, or you're changing somebody's working connection to test a theory.
You probably don't need to care
If you're not moving traffic through a tunnel, none of this will ever reach you. It shows up in VPNs, in WireGuard and OpenVPN setups, in proxies, in container overlays, and anywhere a cloud provider quietly hands you a 1450 byte path instead of 1500. If you're weighing a tunnel against a proxy in the first place, that's a different question and it's covered in mobile proxy vs VPN.
We build and sell software that runs traffic over exactly these tunnels, so weigh the advice accordingly. The MTU arithmetic is the same whoever's tunnel it is, and it'll be the same next year.
The general lesson outlasts the specific number. When a workaround covers one protocol completely, it hides the underlying bug until something arrives speaking the other one. QUIC is that something, and it's already the default in the browser your users are holding. The same reasoning applies one layer up when you pick between SOCKS5 and HTTP proxies, and it's worth knowing before you wire either into Puppeteer, Playwright or Selenium.
Frequently asked questions
What MTU should I use for WireGuard over a mobile connection?
1360 is a safe starting value and it survives most carrier paths. WireGuard defaults to 1420, which assumes a full 1500 byte path underneath it, and a mobile carrier rarely gives you one. If you want the exact number rather than a safe one, measure the path with a do-not-fragment ping sweep and then set the tunnel below what you measure, because the tunnel's own header comes off that figure too.
Why does MSS clamping not fix QUIC?
Clamping works by rewriting an option in the TCP handshake so both ends agree on a smaller segment before any data moves. QUIC runs over UDP, which has no such handshake and no such option, so there's nothing for the clamp to rewrite. The result is a tunnel where every TCP connection is protected and every QUIC connection is not.
How do I tell whether MTU is the problem and not the proxy?
Run the same request twice, once with a command line tool and once with a browser or an HTTP/3 capable client. If the command line succeeds and the browser stalls on the same endpoint through the same tunnel, that asymmetry points at MTU rather than at authentication, routing or the proxy itself. A genuine proxy fault fails both.
Does lowering the MTU slow the connection down?
Slightly, and far less than the failure it prevents. Smaller packets mean marginally more header overhead per byte carried, on the order of a few percent. Set against transfers that never complete at all, that trade isn't close.
Why do the metrics look healthy while users report slow sites?
Because an oversized packet is dropped in transit and the ICMP message that would explain the drop is filtered by most carriers, so the sender learns nothing and simply retransmits. The connection stalls instead of erroring. Nothing on your side records a request that never arrived, which is why dashboards stay green through the whole incident.
Skip the tunnel arithmetic
Our endpoints ship with the MTU already set for carrier paths, so the browser behaves the same as curl. Start on the free trial and see.