Dual-WAN load balancing
A small but unusual application of the splitting engine: instead of splitting across VPN tunnels for privacy, you split across raw WAN links for bandwidth and resilience. The same kernel-side numgen round-robin works, the same per-connection affinity holds, the same failover semantics apply.
A home or small office has two ISP uplinks — for example a fibre line and a 4G LTE backup — and wants:
- Both uplinks used in parallel when both are healthy (cost-effective, twice the aggregate bandwidth).
- Per-connection affinity so each TCP flow sticks to one link (no broken sessions).
- Automatic failover when one link dies — surviving link takes everything.
- No leakage through the broken link if it comes back up briefly with corrupted state.
Hardware & install
Section titled “Hardware & install”Any Linux box with two NICs (or one NIC + a USB Ethernet adapter). Mini-PC works well.
sudo ./scripts/install-gateway.sh --profile vps # no WiFi AP neededNetwork mode: primary (The Gateway is the LAN’s main router).
Bootstrap config
Section titled “Bootstrap config”network: mode: primary wan_iface: eth0 # primary WAN (fibre) lan_iface: eth2 wan_secondary_iface: eth1 # secondary WAN (LTE)
dhcp: enabled: true interface: eth2 range_start: "192.168.10.100" range_end: "192.168.10.250" gateway: "192.168.10.1" dns: "192.168.10.1"Why splitting works for raw WAN links
Section titled “Why splitting works for raw WAN links”The Gateway’s splitting engine operates on endpoints — objects with a type (wireguard, openvpn, socks5, direct), an interface, a weight, and a route-table ID. For dual-WAN balancing, we register two direct endpoints, one per WAN interface, via the splitting API:
curl -X POST https://gateway.lan:8080/api/v1/splitting/endpoints \ -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \ -d '{ "name":"wan-fibre", "type":"direct", "interface":"eth0", "weight":7 }'
curl -X POST https://gateway.lan:8080/api/v1/splitting/endpoints \ -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \ -d '{ "name":"wan-lte", "type":"direct", "interface":"eth1", "weight":3 }'Weights (1–10) bias the round-robin: 7/3 above means 70% of new connections use the fibre, 30% use the LTE. The Gateway installs the necessary ip rule / per-endpoint routing table; you don’t need to write those by hand.
Note that this is a more advanced setup than the VPN-pool case — direct endpoints don’t have the kill-switch protection that tunnel endpoints get. Pair with fail_policy: fail-open (this isn’t privacy, it’s bandwidth).
Activate the splitting engine
Section titled “Activate the splitting engine”Once endpoints are registered, activate the engine. It distributes new connections across registered endpoints at the configured privacy level; existing connections stay on their original link via conntrack affinity:
# Set the splitting privacy level — 1 means session-affinity round-robin# with no padding / cover / shaping overhead. Right for bandwidth balancing.curl -X PUT https://gateway.lan:8080/api/v1/splitting/level \ -H "Authorization: Bearer $API_KEY" \ -d '{"level":1}'
# Activate — refuses with 409 if no endpoints are registeredcurl -X POST https://gateway.lan:8080/api/v1/splitting/activate \ -H "Authorization: Bearer $API_KEY"Privacy level 1 is the right choice here — per-session affinity so a single TCP flow doesn’t bounce between links mid-connection, but no padding / cover / shaping overhead. Level 0 disables splitting entirely; levels 2+ start adding overhead suited to a VPN-pool case, not a WAN-balance one.
Failover
Section titled “Failover”When one WAN dies:
- Its latency probe fails N times → the splitting engine marks the endpoint unhealthy within ~10 seconds.
- All new connections route to the surviving link (the slot map drops the unhealthy endpoint).
- Existing connections on the dead link will eventually time out — applications retry on the surviving link.
- When the dead link recovers, the probe succeeds → the endpoint is re-added to the slot map at its full weight on the next rotation cycle.
Failover requires no manual intervention. No script, no cron, no monit. The reconciler does the right thing.
Per-app stickiness for problematic services
Section titled “Per-app stickiness for problematic services”Some apps don’t tolerate per-connection routing variance — banking sites that lock a session to a source IP, for instance. A routing policy pins the matched domain to one link, bypassing the splitter for that destination. Routing policies run before the splitter so any matched packet is marked + routed before the splitter sees it; no extra precedence work is needed here.
curl -X POST https://gateway.lan:8080/api/v1/routing/policies \ -H "Authorization: Bearer $API_KEY" -d '{ "name": "bank-pin-fibre", "priority": 110, "enabled": true, "source_type": "domain", "source_value": "*.bank.example", "hops": [{"type":"interface","target":"eth0"}], "fail_policy": "fail-open" }'
curl -X POST https://gateway.lan:8080/api/v1/routing/policies/apply \ -H "Authorization: Bearer $API_KEY"The bank-pin-fibre policy wins on domain match before traffic ever reaches the splitter. Everything else falls through to the round-robin pool.
What happens when
Section titled “What happens when”| Scenario | Result |
|---|---|
| Both WANs healthy | New connections split 70/30 across fibre / LTE. Established connections stick to their link. |
| Fibre goes down | Within ~10s, all new connections route via LTE. Existing fibre flows time out and retry. |
| Fibre flaps (down → up → down) | The endpoint is marked unhealthy → recovered → unhealthy. The splitter’s hysteresis stops thrashing — minimum 60s healthy before re-adding. |
| Both WANs down | No internet (obviously). Kill-switch never engaged because fail_policy: fail-open for this profile — but there’s nothing to route to anyway. |
What this is not
Section titled “What this is not”This isn’t channel bonding (a single TCP flow split across two links). That requires both endpoints to cooperate. This is per-connection load balancing — each flow uses one link, but new flows distribute across links. For ~95% of household / small-office traffic, this is what you want and it actually works.
Next steps
Section titled “Next steps”Once the dual-WAN split is healthy, what to do next:
- Wrap a VPN around it. Layer privacy classes and tunnel pools so failover applies to both the upstream link and the egress provider. See traffic splitting.
- Watch the right alerts. Enable
tunnel-flapandtunnel-downso a flaky uplink pages on-call instead of silently bleeding traffic to the worse link. See Alerts. - Pin sensitive flows. Use a higher-priority policy for banking / work to force them onto the more trusted link regardless of weighting. See Routing policies.
Related
Section titled “Related”- Traffic splitting — the engine that powers the per-connection distribution, just with WAN interfaces in place of VPN tunnels.
- VPN tunnels — register a raw WAN interface as a tunnel of type
interface. - Routing policies — pinning a domain to one link with higher priority than the balanced pool.