GRE Tunnels in Production: MTU, Fragmentation, and the Pitfalls Nobody Warns You About

Kicked TeamFebruary 24, 20267 min read

GRE is one of the oldest tunneling protocols still in daily production use, and it refuses to die for a good reason: it is simple, it is everywhere, and it carries anything. It is also the source of a very specific class of outage — the one where ping works, small requests work, and anything involving a full-sized packet hangs forever.

This post is the checklist we wish someone had handed us the first time we brought up a GRE tunnel that "worked" and then quietly didn't.

Where GRE Still Earns Its Place

GRE (Generic Routing Encapsulation, protocol 47) wraps a packet in a new IP header plus a small GRE header and sends it point-to-point. No encryption, no authentication, no session state. That simplicity is exactly why it survives:

  • DDoS scrubbing return paths. This is the canonical modern use. A scrubbing provider announces your prefix, absorbs the attack, and returns clean traffic to you — over a GRE tunnel, because your actual upstream path is being advertised elsewhere. We covered the surrounding architecture in our DDoS mitigation post.
  • Connecting network islands. Two sites, no shared L2, no MPLS budget — a GRE tunnel over the public internet gives you an interface you can route over.
  • Carrying routing protocols where policy blocks them. OSPF adjacencies and BGP sessions run happily inside a tunnel across an underlay that would never forward your multicast hellos or accept your prefixes natively.

The MTU Math You Must Do

This is where most GRE deployments go wrong, so let's do the arithmetic explicitly.

A standard Ethernet path gives you a 1500-byte IP MTU. GRE encapsulation adds:

  20 bytes  outer IPv4 header
+  4 bytes  GRE header
= 24 bytes  overhead

So the largest inner packet that fits without fragmentation is:

1500 − 24 = 1476 bytes  → your tunnel MTU

For TCP, subtract the inner IP and TCP headers to get the MSS:

1476 − 20 (IP) − 20 (TCP) = 1436 bytes  → your TCP MSS

Two caveats that shift these numbers: if you use a GRE key (common when multiplexing tunnels between the same endpoints), add 4 more bytes of overhead — 1472/1432. If the outer transport is IPv6, the outer header is 40 bytes, so the overhead is 44 and the numbers drop to 1456/1416.

Bringing up the tunnel on Linux with the MTU set correctly from the start:

ip tunnel add gre1 mode gre local 198.51.100.2 remote 203.0.113.1 ttl 255
ip link set gre1 up mtu 1476
ip addr add 10.0.0.1/30 dev gre1

If you leave the MTU at whatever the kernel picks and it disagrees with the far end, you have built a fragmentation machine.

PMTUD Blackholes: Why It "Almost" Works

In theory you don't need to care about any of this, because Path MTU Discovery handles it: a host sends a full-sized packet with DF set, the router in front of the tunnel can't fit it, and it replies with ICMP Fragmentation Needed telling the sender to use 1476. The sender shrinks its packets. Everyone is happy.

In practice, that ICMP message has to travel back across the internet to the sender — and somewhere along that path there is a firewall configured by someone who read that "ICMP is a security risk" and dropped all of it. Now the sender never learns about the smaller MTU. It keeps retransmitting full-sized packets with DF set, the tunnel router keeps silently discarding them, and the connection hangs.

The symptom is unmistakable once you've seen it: ping works, the TCP handshake works, small transfers work, and the first full-sized packet — a TLS certificate, an HTTP response body, an scp of anything real — stalls forever. The handshake packets are small; the data packets are not.

You cannot fix everyone else's ICMP filtering. So don't depend on it.

MSS Clamping: The Fix That Actually Holds

Since TCP negotiates its maximum segment size in the SYN packets, and those SYN packets pass through your router, you can rewrite the advertised MSS in flight so both ends never attempt a packet that won't fit. This is MSS clamping, and it is not optional for a production GRE tunnel — it is part of bringing the tunnel up.

With nftables:

nft add rule inet filter forward oifname "gre1" \
    tcp flags syn tcp option maxseg size set rt mtu

rt mtu clamps to the route's MTU automatically, which survives you changing the tunnel MTU later. The iptables equivalent:

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

Clamp in both directions (traffic entering the tunnel and traffic leaving it) and remember it only helps TCP. UDP-based protocols — QUIC, VPNs, DNS with large responses — still depend on correct MTU configuration and working PMTUD, which is one more reason to set the tunnel MTU honestly instead of hoping.

Keepalives: Linux Doesn't Have Them

Cisco-world GRE has a keepalive mechanism (a cleverly self-addressed packet the far end bounces back). Linux GRE does not implement it. A Linux GRE tunnel is stateless: the interface stays "up" even if the far endpoint has been powered off for a week, and your routes over it keep blackholing traffic accordingly.

So liveness has to come from a layer above the tunnel:

  • run a routing protocol over it (OSPF or BGP hellos double as liveness),
  • add BFD if your routing stack supports it, or
  • at minimum, a ping-based monitor that withdraws the route when the far side stops answering.

Static routes pointed into an unmonitored GRE tunnel are a silent failure waiting for its moment.

When Not to Use GRE

  • When the traffic needs privacy. GRE is cleartext — every payload byte is readable on the wire. Either run IPsec over the tunnel or skip the layering and use WireGuard, which gives you encryption, keepalives, and NAT traversal in one tool. For new site-to-site links with no legacy constraints, WireGuard is usually the better default.
  • When you need L2. GRE carries L3. If you need to stretch a broadcast domain, that's VXLAN's job (though think hard before stretching L2 at all).
  • When there's NAT in the path. GRE is IP protocol 47 — no ports — and most NAT devices handle it somewhere between badly and not at all.

Lessons

  1. Do the MTU arithmetic before the tunnel carries traffic — 1476 MTU and 1436 MSS on IPv4, less with keys or IPv6 outer headers.
  2. Never depend on PMTUD alone — someone on the path is dropping the ICMP you need.
  3. Clamp MSS at the tunnel router, both directions — it is part of the tunnel config, not an optimization.
  4. Monitor liveness above the tunnel — Linux GRE will happily stay "up" into a dead endpoint.
  5. Reach for WireGuard when you need privacy, VXLAN when you need L2 — GRE's niche is plaintext L3 simplicity.

GRE tunnels are part of how scrubbed traffic gets back to networks like ours — if you're wrestling with tunnels, MTU mysteries, or edge networking generally, we do this for a living.