Your WireGuard tunnel works fine at home, then dies the moment you join a hotel, campus, cafe or mobile-carrier network. Below I show how to prove the network is the problem, hide WireGuard inside traffic those networks allow, and use split tunneling so only the traffic that needs the tunnel goes through it. I assume you already have a server running from my WireGuard server setup guide.
WireGuard split tunneling: what it actually does
On Linux, AllowedIPs is not a filter on top of your routing. wg-quick turns every AllowedIPs entry into a route on the tunnel interface. It is an allow-list: put a network in AllowedIPs and only that network goes through the tunnel.
WireGuard split tunneling means listing the networks that should go inside the tunnel in AllowedIPs; everything else stays on your normal connection. The AllowedIPs list is the split tunnel.
AllowedIPs = 0.0.0.0/0 is the full tunnel. The popular 0.0.0.0/1 plus 128.0.0.0/1 trick does not exclude anything, because those two halves still cover all of IPv4. To exclude a host or subnet, add a more specific route so longest-prefix match sends it through your normal gateway:
PostUp = ip route add 198.51.100.0/24 via 192.168.1.1
PreDown = ip route del 198.51.100.0/24 via 192.168.1.1
Set Table = off in [Interface] if you want to manage routing yourself. Only add a DNS line if you want the tunnel's resolver.
Per-app splitting depends on the platform:
| Platform | Per-app split tunneling | What to use |
|---|---|---|
| Linux | No | AllowedIPs, extra routes, Table = off, or the Amnezia client |
| Android | Yes, official app | "Excluded Applications" (ExcludedApplications in the config) |
| iOS | No | On-Demand rules instead |
| Windows / macOS | Not in the official app | Tunnel follows AllowedIPs; Amnezia client for per-app routing |
Why WireGuard stops working on some networks
WireGuard was built to be fast, not hidden. The handshake initiation is always exactly 148 bytes, the response 92 bytes and the cookie 64 bytes, and every packet starts with a fixed 4-byte message type. For deep packet inspection, that shape is trivial to fingerprint.
Networks usually break it in one of three ways: they drop all UDP (common on hotel and campus Wi-Fi), throttle UDP until the tunnel crawls, or allow only TCP 80 and 443.
Before you change anything: three quick checks
First, run sudo wg show. If "latest handshake" is empty, your packets are not getting through, and it is not a key typo when the same config works at home.
Second, connect your laptop to your phone's hotspot. If the tunnel comes up instantly, the server is fine and the network is at fault.
Third, test UDP and TCP 443 separately while tcpdump watches the server:
on the server
sudo tcpdump -ni any 'udp port 51820 or tcp port 443'
on the client, from the blocked network
echo test | nc -u -w1 203.0.113.2 51820
nc -vz -w3 203.0.113.2 443
If nothing arrives on UDP but the TCP 443 attempt shows up, you are on a TCP-only network.
Then try the cheapest fix. Many networks only filter UDP 51820 or unknown high ports. Set ListenPort = 443 in the server config from my WireGuard setup guide, open UDP 443 in the firewall and update the client Endpoint. No extra software.
Finally, rule out MTU. If the handshake works but nothing loads, run ping -M do -s 1400 <server-ip>. If it fails, reduce the size until it passes and lower the tunnel MTU to match.
Fix 1: udp2raw, WireGuard in a fake TCP connection
udp2raw (MIT) uses raw sockets to wrap UDP in encrypted FakeTCP, UDP or ICMP. In faketcp mode on port 443, firewalls see an ordinary TCP connection. It adds AES-128-CBC encryption, HMAC-SHA1 authentication and anti-replay protection, and it survives Wi-Fi changes.
Server:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server private key>
MTU = 1342
PreUp = udp2raw -s -l 203.0.113.2:443 -r 127.0.0.1:51820 -k "shared secret" -a >/var/log/udp2raw.log 2>&1 &
PostDown = killall udp2raw || true
[Peer]
PublicKey = <client public key>
AllowedIPs = 10.8.0.2/32
Client:
[Interface]
Address = 10.8.0.2/24
PrivateKey = <client private key>
MTU = 1342
PreUp = udp2raw -c -l 127.0.0.1:50001 -r 203.0.113.2:443 -k "shared secret" -a >/var/log/udp2raw.log 2>&1 &
PostUp = ip route add 203.0.113.2 via 192.168.1.1
PreDown = ip route del 203.0.113.2 via 192.168.1.1
PostDown = killall udp2raw || true
[Peer]
PublicKey = <server public key>
Endpoint = 127.0.0.1:50001
AllowedIPs = 0.0.0.0/0
Both ends drop to MTU 1342 because udp2raw cannot carry payloads as large as plain WireGuard; go lower if any link in the path is smaller. On the server, -r 127.0.0.1:51820 forwards into your real WireGuard listener. The -a flag adds the iptables rules udp2raw needs, and forgetting it is the most common mistake.
That PostUp route fixes a gotcha from the project wiki: with 0.0.0.0/0 in AllowedIPs the client needs an exception for the server's public IP, or the tunnel tries to carry its own traffic and loops. Replace 192.168.1.1 with your real gateway.
Honest limits: it needs root, and the last tagged release is 20230206.0 (February 2023). The repo still gets commits but moves slowly, so build from source. Advanced DPI can fingerprint the FakeTCP handshake. ICMP mode is a last resort. Fake TCP has no congestion control, so expect a bit more latency and jitter.
Fix 2: AmneziaWG, the obfuscated WireGuard fork
If the handshake starts and then dies, or WireGuard stays blocked for weeks, the network is fingerprinting the protocol. AmneziaWG is a fork of WireGuard-Go (MIT, with a GPL-2.0 kernel module). It keeps the same ChaCha20-Poly1305 cryptography and speed but changes how the traffic looks.
The padding values S1-S4 randomize packet sizes, so the fixed sizes of 148, 92 and 64 bytes disappear. Before each handshake it sends junk packets: Jc sets how many (4 to 12 is recommended), and each one gets a random size between Jmin and Jmax. Version 2.0 added dynamic headers (H1-H4) and signature packets (I1-I5) that can imitate a QUIC initial. Version 3.1 encrypts the low-entropy header fields with ChaCha20 and requires S1-S4 to be at least 12.
The catch: both ends must run AmneziaWG, because it does not talk to stock WireGuard. Clients cover Windows, macOS, Linux, Android and iOS via the Amnezia VPN app (4.8.12.7+) or the native AmneziaWG clients. Expect it to run slightly slower than stock WireGuard, and keep Jmax below your MTU so junk packets do not fragment.
Fix 3: wstunnel, when only HTTPS gets out
wstunnel (Rust, BSD-3-Clause, actively developed) wraps UDP inside WebSocket or HTTP2 traffic on port 443.
server
wstunnel server wss://0.0.0.0:443 --restrict-to 127.0.0.1:51820
client
wstunnel client -L "udp://51820:localhost:51820?timeout_sec=0" wss://203.0.113.2:443
Point the WireGuard client Endpoint at 127.0.0.1:51820. If you run a full tunnel, add the same server-IP route exception. If Nginx already uses port 443, run wstunnel on a local port and proxy WebSocket traffic to it, the same pattern as in my Nginx reverse proxy guide.
The tradeoff: TCP brings head-of-line blocking and the double-TCP problem, so under packet loss it is slower than udp2raw or AmneziaWG. That is another reason to split-tunnel instead of pushing everything through it.
Split tunneling while the tunnel is fragile
An obfuscated tunnel is slower and more fragile than plain WireGuard, so push less through it. Most of my clients on hotel Wi-Fi only need their Nextcloud, Matrix server or office subnet, so I route just that:
`[Peer]
PublicKey = <server public key>
Endpoint = 127.0.0.1:50001
AllowedIPs = 10.8.0.0/24, 192.168.50.0/24
no DNS line in [Interface]: your local resolver keeps working`
With WireGuard split tunneling like this, browsing and video calls use the normal connection. Only private traffic goes through udp2raw or wstunnel. The server's public IP is outside AllowedIPs here, so there is no routing loop.
One honest note: respect your employer's network policy and your local law. These are your own tools for your own server, which is the whole point of self-hosting.
Which fix should you use?
Match your symptom to the fix:
Everything works except UDP 51820: move WireGuard to UDP 443 first. No extra software.
UDP is blocked entirely (hotel, campus, some carriers): udp2raw in faketcp mode on TCP 443.
The handshake starts and then dies, or WireGuard has been blocked for weeks: the network is fingerprinting the protocol, so use AmneziaWG.
Only TCP 443 gets out: wstunnel.
You only need one subnet or one service: use WireGuard split tunneling and skip obfuscation entirely.
Frequently asked questions
Does split tunneling break WireGuard's privacy?
No. WireGuard split tunneling only decides which traffic enters the tunnel, and that traffic stays fully encrypted. Everything else travels normally.
Can I use udp2raw with the official WireGuard app on my phone?
Not on a normal phone. udp2raw needs root and raw sockets, which regular phone apps do not get, so on mobile use the Amnezia app with AmneziaWG instead.
Is AmneziaWG safe and audited?
Largely, yes. AmneziaWG keeps WireGuard's ChaCha20-Poly1305 cryptography and only changes how packets look, and the code is open source. Check the Amnezia repositories for the current audit status.
Will a VPN bypassing a blocked network get me banned?
It can. On an employer or campus network, bypassing controls may breach an acceptable use policy, so read the rules before you tunnel around them.
If you would rather not fight MTU values and iptables rules from a hotel lobby, I can do it for you. Since 2020 I have deployed 30+ self-hosted Nextcloud, Jitsi, Matrix and VPN setups for small clients. Take a look at my self-hosting and VPN setup services and send me your server details.
Top comments (0)