Tech Digest

Guide

What MTU should a WireGuard tunnel use, and why do some sites hang over it?

Take the smallest MTU on the path to the endpoint and subtract 80, or 60 if the endpoint is IPv4 only, and set that on both ends. We built a lab tunnel over a 1492-byte link and broke it on purpose: ping kept working while TLS stalled, and lowering only the client did not fix it.

Last reviewed

What MTU should a WireGuard tunnel use?

Take the smallest MTU on the path between the two endpoints and subtract 80 bytes if the endpoint is reached over IPv6, or 60 if it is reached over IPv4: 1420 on a 1500-byte Ethernet, fibre or cable uplink, 1412 on PPPoE over IPv6, 1400 over DS-Lite IPv4. Set it on both ends, because WireGuard pads each packet up to the sending side's MTU. 1280 is the lowest value that still carries IPv6 inside the tunnel and the fallback when you cannot measure.

Take the smallest MTU on the path between the two WireGuard endpoints and subtract 80 if the endpoint is reached over IPv6, or 60 if over IPv4 only. On a 1500-byte Ethernet, fibre or cable uplink that gives 1420, the default everyone sees. Set it on both ends, not only the client, and use 1280 if you cannot measure.

If the tunnel connects and pings but some sites or TLS handshakes hang forever, the MTU is too big for the path.

Where 1420 comes from#

wg-quick's set_mtu_up takes the MTU of the route to each peer endpoint, or of the default route if no endpoint is known, and runs ip link set mtu $(( mtu - 80 )). The manual calls this "usually a sane choice"; an MTU = line overrides it.

The 80 is the IPv6 worst case, which Tailscale's source spells out field by field:

Part of every WireGuard packetBytes
Outer IPv6 header (IPv4: 20)40
UDP header8
Type, key index and counter16
Poly1305 authentication tag16
Total over IPv680
Total over IPv460

So both circulating answers, "1420 is always right" and "PPPoE needs 1412", are wrong half the time. Over an IPv4 endpoint, 1420 plus 60 is 1480, which fits a 1492-byte PPPoE link. Over IPv6, 1420 plus 80 is 1500, 8 bytes too many. 1412 is right for IPv6 and wastes 20 bytes per packet on IPv4.

The automatic value also sees only the first hop, unless the kernel has already learned a path MTU for that route: a machine on a LAN behind a PPPoE router has a 1500-byte route and gets 1420. A hub whose peers have no Endpoint line, like an wg-easy server, uses the default route and gets 1420 whatever uplink each client is on. In our lab the client on a 1492-byte link came up at 1412 and its server at 1420.

The last column is what wg-quick picks on a computer behind the router, which sees a 1500-byte route whatever the uplink.

UplinkPath MTUWireGuard MTU, IPv4 endpointWireGuard MTU, IPv6 endpointwg-quick on a LAN machine
Ethernet, fibre, cable (native)1500144014201420
PPPoE1492143214121420: fits IPv4, too big for IPv6
DS-Lite, IPv6 link at 1500IPv4: 1460, IPv6: 1500140014201420: too big for IPv4
DS-Lite over PPPoEIPv4: 1452, IPv6: 1492139214121420: too big for both
LTE or 5Gwhatever the carrier sets; 3GPP's suggested value is 135812981278, below the 1280 floor1420: usually too big
WireGuard inside a 1420 WireGuard tunnel1420136013401340 if the route to the inner endpoint goes through the outer tunnel

The PPPoE figure is RFC 2516's: the PPP MTU "MUST NOT be greater than 1492". DS-Lite carries IPv4 inside IPv6, 40 bytes more, and RFC 6333 makes the home router fragment rather than drop what no longer fits, so an oversized tunnel over DS-Lite IPv4 still works, as fragments. A server on DS-Lite has no public IPv4 address of its own and is reached over IPv6 anyway.

The LTE row is the soft one. 3GPP TS 23.060 says a link MTU of 1358 is small enough in most network deployments, derived from GTP inside IPsec over a 1500-byte transport network. What a carrier actually sets varies, so test on mobile above all. If the path really is 1358, only an IPv4 endpoint carries a 1280-byte inner packet in one piece.

Why ping works and TLS hangs#

We built the failure in network namespaces on kernel 5.4 with its WireGuard module and wireguard-tools v1.0.20250521: a client behind a 1492-byte link standing in for PPPoE, a server, and an HTTPS server behind it with a 4096-bit RSA certificate. Each run fetched a 3-byte and a 200 KB file with an 8-second timeout.

SetupPingHTTPS
IPv6 endpoint, auto MTU (client 1412, server 1420)WorksWorks
IPv6 endpoint, both ends forced to 1420WorksWorks, while the router's Packet Too Big message reached the server
Same, router's Packet Too Big messages droppedWorksBoth files time out, TLS handshake never finishes
IPv4 endpoint, both at 1420 over 1492WorksWorks, also with fragments dropped
IPv4 endpoint, both at 1420 over a 1452-byte pathWorksWorks: the router fragmented the outer packets
Same, fragments droppedWorksBoth files time out

A default ping is 84 bytes. The first full-size packet in an HTTPS connection carries the server's certificate, which is why the 3-byte file failed exactly like the 200 KB one.

IPv4 and IPv6 differ because WireGuard's socket.c sends outer IPv4 packets with the don't-fragment flag cleared, so a router splits an oversized packet instead of dropping it. IPv6 routers never fragment; the sender has to receive a Packet Too Big and shrink. An oversized tunnel therefore works until something on the path drops IPv4 fragments or filters ICMPv6.

Lowering only the client did not fix it#

With the client at 1412 and the server left at 1420, the IPv6 path still hung. tcpdump on the router showed 1,452-byte UDP payloads coming from the server: 1420 bytes of inner packet plus WireGuard's 32.

The cause is in the kernel's send.c. WireGuard pads every packet to a multiple of 16 bytes, capped at the sending interface's MTU. The client's MTU limited the server's TCP segments to 1412-byte packets, the server's 1420 MTU let padding round them up to 1420, and 1420 does not fit. With the client at 1408, a multiple of 16, the same setup worked.

So on a hub, lower the server's interface MTU to the smallest value any client needs, as well as the client's own, or give clients on bad links a multiple of 16.

Testing your own path#

From the client, with the tunnel down, find the biggest packet that reaches the server with fragmentation forbidden. -s is the payload; add 28 for IPv4 or 48 for IPv6.

bash
# IPv4 endpoint: 1464 + 28 = 1492 bytes
ping -M do -c 3 -s 1464 vpn.example.com
# IPv6 endpoint: 1444 + 48 = 1492 bytes
ping -6 -M do -c 3 -s 1444 vpn.example.com

Raise -s until replies stop. On our 1492-byte link, 1464 worked and 1465 failed locally with ping: sendmsg: Message too large. When the small link is further away the error may never come back, so the limit is the largest size that still gets replies. The -M do flag is iputils ping; BusyBox ping, as in Alpine-based Docker Engine images, rejects it.

Subtract 60 or 80 from that packet size, set the MTU on both ends, and check inside the tunnel with the MTU minus 28:

bash
ping -M do -c 3 -s 1384 10.8.0.1   # for MTU 1412

This works even when ICMP is filtered, because a too-large tunnel packet just gets no reply. In our broken setup at 1420, -s 1380 replied and -s 1381 did not: padding again, 1409 bytes rounded up to the cap.

Where wg-easy v15 keeps the MTU#

Not in an environment variable: neither the optional configuration page nor the unattended setup variables list one. In the v15.5.0 source the value lives in the database, seeded at 1420 for the interface and for new clients, in three places in the web UI:

  • Admin Panel, Interface, MTU: "MTU WireGuard will use", the server's wg0.
  • Admin Panel, Config, MTU: "MTU clients will use (only for new clients)".
  • Each client's edit page, MTU: the value written into that client's config.

wg-easy writes the MTU = line explicitly, so wg-quick's calculation never runs. Changing the Config default leaves existing clients at 1420: edit each one, then import the new config or rescan the QR code on the device. Saving the Interface page runs wg syncconf on the stripped config, and wg-quick strip removes MTU, so in the source the running interface keeps its old value until Restart Interface, which disconnects every client. We read that in the code and did not test it; ip link show wg0 inside the container shows what is in force.

When the fix is MSS clamping instead#

The same symptom can appear with a correct tunnel MTU, when a router puts a whole LAN through WireGuard, as in a site-to-site setup. Machines on that LAN announce a 1460-byte TCP segment size, websites send 1500-byte packets that do not fit the 1420 tunnel, and the far end of the tunnel has to send an ICMP "fragmentation needed" back to the website.

With that ICMP dropped at the web server, the first HTTPS connection from the LAN machine stalled at the handshake in each of our three runs; later ones sometimes got through, so a site that loads on the second try proves nothing. Clamping the MSS on the client router fixed it from the first connection:

bash
iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN \
  -j TCPMSS --clamp-mss-to-pmtu

Clamping does nothing when the encrypted packets themselves are too big for the uplink. A device running the WireGuard client itself, such as a phone, already announces a segment size that fits.

When this answer stops applying#

  • UDP inside the tunnel. TCP is the only traffic the segment size limits. Large UDP packets still depend on the MTU being right.
  • Other WireGuard implementations. The padding and don't-fragment behaviour above is the Linux kernel module's. wireguard-go, which Tailscale's client and therefore Headscale networks run on, was not tested.
  • Router operating systems. OpenWrt, OPNsense and MikroTik have their own MTU fields and were not tested. The arithmetic is the same.
  • We did not test real PPPoE, DS-Lite or LTE links, only links set to their sizes, or wg-easy itself.

What to do next#

Run both ping tests from the device on its worst connection, usually a phone on mobile data, set the result on the server's interface and on that client, and use 1280 for anything you cannot measure. Headscale vs wg-easy covers what else differs between a WireGuard hub and a mesh, and Remote access without port forwarding whether you can reach the endpoint at all behind CGNAT or DS-Lite. If the tunnel's subnet collides with a Docker network, Docker subnet clashes and address pools fixes that, and DNS for self-hosters makes names resolve on both sides of the tunnel.

Questions#

Why is the WireGuard default MTU 1420?

wg-quick takes the MTU of the route to the endpoint, or of the default route, and subtracts 80. On a 1500-byte link that gives 1420. The 80 bytes are the worst case: a 40-byte IPv6 header, an 8-byte UDP header and 32 bytes of WireGuard (a 16-byte data header and a 16-byte authentication tag). Over an IPv4 endpoint the real overhead is 60, so 1420 leaves 20 bytes spare. wg-easy does not calculate anything: it writes 1420 into the config.

Why does ping work over WireGuard but websites do not load?

A default ping is 84 bytes and fits any tunnel. A TLS handshake sends the server's certificate in full-size packets. If those packets, plus WireGuard's 60 or 80 bytes, are bigger than a link on the path, and the ICMP error or the fragments do not get back, they vanish. In our lab with a 1420 tunnel over a 1492-byte IPv6 path, ping replied every time and curl timed out with the TLS handshake never finished, even for a 3-byte file.

Where do I change the MTU in wg-easy v15?

In the web UI, not in the compose file: v15 has no MTU environment variable. Admin Panel, Interface, MTU sets the server's wg0. Admin Panel, Config, MTU sets the value for clients created afterwards. Existing clients each have their own MTU field on their edit page, and the device has to import the config again. In the v15.5.0 source, saving the interface runs wg syncconf, which does not change the MTU of the running interface, so use Restart Interface afterwards.

Should I use MTU 1280 for WireGuard?

Use it when you cannot measure the path, or when the tunnel runs inside another tunnel. 1280 is the IPv6 minimum link MTU in RFC 8200, and Linux removed the IPv6 address from our WireGuard interface at 1279, so it is the floor for a tunnel that carries IPv6. It fits any path of 1360 bytes or more. Tailscale's client uses 1280 by default for the same reason. The cost is about 11 percent more packets than at 1420 for the same TCP data, since each segment carries 1,240 bytes instead of 1,380.

Does MSS clamping fix WireGuard MTU problems?

It fixes one of the two problems. When devices on a LAN behind a WireGuard router advertise a 1460-byte MSS, websites send packets too big for the tunnel and rely on an ICMP message to shrink them. Clamping the MSS on the router fixed that case in our lab. It does not help when the tunnel MTU itself is too large for the path, because then the encrypted packets are what is too big. Lower the MTU for that.

What MTU should WireGuard use over PPPoE?

1412 if the endpoint is reached over IPv6, 1432 if it is reached over IPv4 only. PPPoE limits the path to 1492 bytes under RFC 2516. Over IPv4 the default 1420 already fits, since 1420 plus 60 is 1480; our lab tunnel over a 1492-byte link moved data at 1420 even with fragments dropped. Over IPv6, 1420 plus 80 is 1500, which is 8 bytes too many.

Sources#

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.