Multi-gig line, single-gig VPN: running three WireGuard tunnels at once so the traffic that can spread actually uses the connection

Discussion

The wall everyone with a fast line hits eventually: you pay for multi-gig, your NICs do multi-gig, iperf across the LAN does multi-gig — and then everything goes through one WireGuard tunnel and sits at <<your measured single-tunnel figure, e.g. ~1.1 Gbps>>. The line isn't the problem and the NIC isn't the problem. One tunnel is one tunnel: one peer, one endpoint, one queue's worth of crypto, and whatever the far end is willing to give a single connection.

You can't make one tunnel faster from your side. You can run more than one. So I wrote a gateway that runs up to five WireGuard tunnels at once and spreads traffic across all of them with ECMP, and the machines behind it just use it as their default route.

Read this part before you get excited, because it decides whether this is any use to you.

ECMP hashes per connection, not per packet. fib_multipath_hash_policy = 1 puts source/destination ports in the hash, so each TCP connection is assigned a tunnel and stays there for its whole life. Consequences:

  • One big single-stream download will never exceed one tunnel. Not a little faster — identical. If your use case is one large file over one connection, stop reading, this cannot help you and neither can anything else short of a provider with faster servers.
  • Anything that opens many connections scales. Usenet with 40 connections, a browser, parallel HTTP, several download clients at once, a multi-stream speedtest. Those spread across the tunnels and you get the sum.
  • Per-packet striping would fix the single-stream case and is a terrible idea over independent tunnels: different paths, different latency, out-of-order delivery, and TCP reacting to reordering as though it were loss. Per-flow is the correct trade and it's the one every sane multipath setup makes.

The detail that silently wastes the whole exercise: leave fib_multipath_hash_policy at 0 and the kernel hashes on addresses only. Every connection to a given server then lands on the same tunnel, and you get exactly one tunnel's throughput while three tunnels sit up, handshaking and idle, with nothing reporting an error. The daemon sets it, re-checks it, and reports it as a problem if something resets it — that one sysctl is most of the difference between this working and looking like it works.

CPU is the next ceiling, and it arrives sooner than you think. Three tunnels is three times the encryption. Multi-gig WireGuard is genuinely CPU-hungry, and on a low-power box you'll hit the processor long before the line. Mine does <<your aggregate figure and the CPU it's on, e.g. ~2.8 Gbps aggregate on a 12th-gen i5>>. Worth knowing before you buy an N100 for it.

Topology, because this isn't a client you install on your desktop. It's a gateway for a subnet that is not your LAN. The clients get their own network and point at it as their route out. It refuses to start if you point it at your LAN or at the subnet holding the host's own address, because that configuration routes the machine's own replies into a tunnel and it goes silent while looking perfectly healthy. In practice that means containers, VMs, or a dedicated VLAN behind it — not your gaming PC sitting on the main LAN.

The rest is what you'd want if it's carrying everything: with all tunnels down the client subnet has no path out at all rather than a quiet fallback to your ISP, and leak-test proves that from a network namespace on the client bridge instead of asserting it. DNS is redirected in the kernel, so a machine pointed at 8.8.8.8 can't leak its lookups while its traffic stays correctly tunnelled. Tunnels that die get restarted, tunnels that are up but have stopped handshaking get cycled, and the routing rules and their tables are re-checked every pass — a tunnel that drops and returns otherwise leaves one client with a rule pointing at an empty table and no way out, while everything else looks fine.

Before you bother:

  • Closed source. Free, single static Go binary, no runtime dependencies, zero third-party modules — but it runs as root next to your VPN keys and you're trusting a binary. Saying it up front rather than letting you find out.
  • Linux, root, WireGuard only. You supply your provider's config files. No OpenVPN, no built-in server lists.
  • Five tunnels max, three by default. Five WireGuard configs eats an entire Mullvad device allowance, which is the real limit for most people rather than anything technical.
  • It checks a signed status file hourly — a plain GET — from a Cloudflare Worker, with the copy in the public repo as fallback. No install ID, no cookie, nothing stored per request; requests ÷ 24 tells me how many installs are still alive. Block the host and yours carries on from the repo copy.
  • No port forwarding.

https://github.com/wonderingStars/bondvpn — binaries with SHA256SUMS under Releases, an install.sh, and a SETUP.md that goes from a bare box to a working stack.

Happy to answer topology questions. If you've got a multi-gig line and you've measured your single-tunnel ceiling, I'd genuinely like to hear the number.

Posted Aug 12, 2026, 10:56 AM UTC

026
Loading comments…