The point first, so you can stop reading if you've already guessed it: a tunnel that lets small packets through fine but hangs on anything large is almost always an MTU problem. It was an MTU problem. It is always an MTU problem.
Here is the shape of it. I had a WireGuard link between a homelab box and a small VPS, set up so I could reach an internal service from outside. SSH worked. ping worked. curl to a tiny health endpoint worked. Then I tried to pull a database dump over the same path and it hung after the first few kilobytes, sat there for thirty seconds, then died. Classic. Anything that fits in one packet is fine; anything that fills the pipe stalls.
Why it presents this way
The handshake of most protocols is small. Auth, headers, a "200 OK", these all fit comfortably inside a single segment. The moment you start moving real data, TCP fills packets to the path MTU and pushes them out. If a hop along the way can't carry a packet that big and the "please fragment" signal is being eaten, the packet is silently dropped. The sender waits, retransmits the same too-big packet, and waits again. From the application's point of view the connection just freezes.
The mechanism that's meant to save you is Path MTU Discovery. The router that can't forward the oversized packet sends back an ICMP "fragmentation needed" message, the sender shrinks its segment size, and everyone gets on with their day. The trouble is that an enormous number of firewalls and security groups block ICMP wholesale, because someone once read that ICMP is "dangerous" and turned it all off. So the feedback never arrives. This is the famous PMTUD black hole, and it is responsible for an unreasonable share of "the network is broken but only sometimes" tickets.
WireGuard makes it easier to hit because it adds its own overhead. Every packet you send gets wrapped: an outer IP header, a UDP header, and the WireGuard header on top. Over IPv4 that's commonly around 60 bytes of encapsulation. So if your underlying path is the usual 1500, the most you can actually carry inside the tunnel is roughly 1440, and WireGuard's default interface MTU reflects that. But if the real path underneath is already smaller, say there's a PPPoE link or another tunnel in the way, then 1440 is still too big and you're back in the black hole.
Finding it
The quick confirmation is to send a packet of a known size with the "don't fragment" bit set and watch it fail:
# 1472 bytes payload + 28 bytes ICMP/IP header = 1500 on the wire
ping -M do -s 1472 10.10.0.1
# ping: local error: message too long, mtu=1440
Walk the size down until it stops complaining and you've found your real ceiling. On this link, anything over about 1400 of payload vanished without a sound, which told me the path underneath was smaller than I'd assumed.
You can also watch it directly. tcpdump on both ends shows the big segments leaving one side and never arriving at the other, with no ICMP coming back to explain why. That absence is the tell. A healthy PMTUD failure is noisy; a broken one is a void.
Fixing it
Two changes, belt and braces.
First, lower the tunnel interface MTU so packets are born small enough to survive:
[Interface]
MTU = 1380
Second, and this is the one that actually rescues the connections going through the box, clamp the TCP maximum segment size to the path MTU on forwarded traffic:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
MSS clamping rewrites the segment size that the two endpoints negotiate during the handshake, so they never agree to send packets bigger than the link can carry. It doesn't rely on ICMP getting back at all, which is exactly why it works when PMTUD has been firewalled into uselessness. With both in place the database dump came across at full speed and the thirty-second hang was gone.
The lesson, again
I have solved this exact problem on a VPN, a Docker overlay, a GRE tunnel, and a flaky DSL line, and every single time I spent the first hour blaming DNS, the firewall, or the application. It is none of those. If small things work and big things hang, measure the MTU before you do anything else. Print it on a sticky note. It will not be the last time.