Your WireGuard tunnel is capped at 600 Mbps on a 10G link and you blame the kernel. The kernel is fine. You're the bottleneck. I've lost count of how many "WireGuard is slow" tickets I've closed with a single ping -M do and a corrected MTU. The crypto is not the problem. Your config is.
This guide is for sysadmins, network engineers, and security engineers who already have WireGuard running and want it to stop embarrassing them. No GUI hand-holding, no Tailscale wrapper nonsense, no "just use wg-quick and pray." We're going to measure, then tune, then measure again — because the only tuning that matters is the tuning you can prove worked.
After reading this you'll be able to build a reproducible iperf3 baseline, identify whether you're CPU-, NIC-, or MTU-bound, and apply a tuned reference config that survives production. The biggest takeaway: baseline benchmark methodology matters more than any single tweak. A 15% MTU fix beats a 5% sysctl guess every time.
:::note[TL;DR]
- WireGuard is fast by default, but "default" means single-thread, conservative MTU, and zero tuning.
- Measure first with
iperf3 -P 8over UDP —speedtest-cliwill lie to you. - MTU is the silent 15% tax. Calculate it, don't copy 1420 from a blog.
- CPU pinning + IRQ affinity fixes most single-tunnel ceilings. Beyond that, scale with multiple tunnels. :::
Prerequisites
- WireGuard installed in-kernel (Linux 5.6+, ideally 6.1+ LTS). DKMS on 4.x is self-harm.
-
iperf3,ethtool,perf,tcpdumpon both endpoints. - Root or
CAP_NET_ADMINon both peers. - A working tunnel you can afford to bounce for 10 minutes.
First: Measure Before You Tune
90% of "WireGuard is slow" tickets are misdiagnosed. Someone ran a TCP speedtest through a browser, saw a number lower than their ISP line rate, and filed a ticket. Establish ground truth before touching a single sysctl.
Building a Reproducible iperf3 Baseline
Run the server on the far peer, listening on the tunnel IP (not the physical interface — you want to measure the tunnel):
# On the server peer, bind to the tunnel address
iperf3 -s -B 10.10.0.1 --logfile /var/log/iperf3-server.log
Then from the client, push UDP with parallel streams. UDP, not TCP — TCP congestion control will hide your tunnel's true capacity behind window scaling and retransmit behavior:
# 8 parallel streams, 30 seconds, target 2 Gbps UDP
iperf3 -c 10.10.0.1 -u -b 2G -P 8 -t 30 --get-server-output
If you see sender at 2 Gbps and receiver at 1.4 Gbps, you have loss — that's your first clue. If both sides match at 1.4 Gbps, you're at the ceiling. Now check wg show on both peers:
wg show wg0
# interface: wg0
# public key: [redacted]
# private key: (hidden)
# listening port: 51820
#
# peer: [redacted]
# endpoint: 203.0.113.5:51820
# allowed ips: 10.10.0.2/32
# latest handshake: 12 seconds ago
# transfer: 4.21 GiB received, 5.87 GiB sent
The transfer counters tell you the actual bytes moved. Compare to ifconfig byte counters on the physical NIC — the delta is WireGuard overhead plus any fragmentation. If that delta is more than ~5%, you have an MTU problem.
The Three Bottlenecks You'll Actually Hit
⚠️ TRUNCATED VERSION
This is an abbreviated cross-post. Full article (all config files, architecture diagrams, images): valtersit.com
🛠️ Partner Tools for Developers
ValtersIT curates partner deals for developers and sysadmins — VPS hosting, security tools, monitoring platforms and dev productivity gear. No filler, no affiliate spam.
Top comments (0)