Operations 15 min read

How to Build Asymmetric Routing with WireGuard on Linux

Learn how to configure WireGuard to create asymmetric routes across multiple machines, using link‑local addresses and static routing, with step‑by‑step examples for three‑node and two‑node setups, and see performance improvements by avoiding costly outbound paths.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
How to Build Asymmetric Routing with WireGuard on Linux
Solution two is considered a better configuration; it uses the same WireGuard setup as solution one but with clearer steps, so readers in a hurry can jump directly to it.

In a peculiar environment where the outbound path can only connect directly via a mobile network while the return path goes through other ISPs, severe jitter and packet loss occur at night. To optimize only the outbound route without changing the return path, we create an asymmetric routing setup using WireGuard as a virtual network.

Asymmetric Routing Across Three Machines

Environment Preparation

The experiment uses three machines:

Local machine A with WireGuard internal IPs 192.168.51.5 and 169.254.1.5

Outbound‑limited machine B with IPs 192.168.51.1 and 169.254.1.1

Outbound‑bypass, higher‑bandwidth machine C with IPs 192.168.51.2 and 169.254.1.2

The desired traffic flow is A → B → C → A.

After installing WireGuard, generate keys and enable packet forwarding:

apt install wireguard wireguard-tools
wg genkey | tee privatekey | wg pubkey > publickey
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.forwarding=1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
sysctl -p

Pairwise Connections

Each pair of machines needs its own WireGuard configuration file (six files total) with Table=off to prevent automatic route changes.

Node A ↔ Node B:

[Interface]
PrivateKey = 
ListenPort = 27000
PostUp = ip addr add 169.254.1.5/32 peer 169.254.1.1/32 dev %i
PostDown = ip addr del 169.254.1.5/32 peer 169.254.1.1/32 dev %i
Table = off
# B
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0
Endpoint = 
PersistentKeepalive = 10

Node A ↔ Node C:

[Interface]
PrivateKey = 
ListenPort = 27001
PostUp = ip addr add 169.254.1.5/32 peer 169.254.1.2/32 dev %i
PostDown = ip addr del 169.254.1.5/32 peer 169.254.1.2/32 dev %i
Table = off
# C
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0
Endpoint = 
PersistentKeepalive = 10

Node B ↔ Node A:

[Interface]
PrivateKey = 
ListenPort = 27000
PostUp = ip addr add 169.254.1.1/32 peer 169.254.1.5/32 dev %i
PostDown = ip addr del 169.254.1.1/32 peer 169.254.1.5/32 dev %i
Table = off
# A
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0

Node B ↔ Node C:

[Interface]
PrivateKey = 
ListenPort = 26002
PostUp = ip addr add 169.254.1.1/32 peer 169.254.1.2/32 dev %i
PostDown = ip addr del 169.254.1.1/32 peer 169.254.1.2/32 dev %i
Table = off
# C
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0
Endpoint =

Node C ↔ Node A:

[Interface]
PrivateKey = 
ListenPort = 27001
PostUp = ip addr add 169.254.1.2/32 peer 169.254.1.5/32 dev %i
PostDown = ip addr del 169.254.1.2/32 peer 169.254.1.5/32 dev %i
Table = off
# A
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0

Node C ↔ Node B:

[Interface]
PrivateKey = 
ListenPort = 26002
PostUp = ip addr add 169.254.1.2/32 peer 169.254.1.1/32 dev %i
PostDown = ip addr del 169.254.1.2/32 peer 169.254.1.1/32 dev %i
Table = off
# thk
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0
Endpoint =

IP Allocation and Static Routing

After establishing pairwise links, create a dummy interface on each host (or add the address directly in PostUp) to receive packets destined for the host itself.

# ip link del dummy
ip link add dummy type dummy
ip addr add 192.168.50.5/32 dev dummy
ip link set dummy up

Set static routes so that traffic from A to C goes through B:

ip route add 192.168.51.2/32 dev <em>wireguard‑AB‑interface</em>
# or ip route add 192.168.51.2/32 via 169.254.1.1

On B, add a route to C:

ip route add 192.168.51.2/32 dev <em>wireguard‑BC‑interface</em>
# or ip route add 192.168.51.2/32 via 169.254.1.2

Testing with ping shows one‑way traffic on B while A receives replies, confirming the asymmetric path.

Ping C result
Ping C result

Further captures on C reveal that responses travel directly from C to A using the link‑local address, completing the asymmetric routing.

B‑C interface
B‑C interface
C‑A interface
C‑A interface

Why Use Link‑Local Addresses

Using 169.x.x.x addresses avoids automatic route entries that would conflict with the manually added static routes; the dummy interface provides a stable endpoint for the point‑to‑point WireGuard links.

Asymmetric Routing with Only Two Machines

The three‑node setup can be reduced to two machines by leveraging a traffic‑forwarding service. Each host runs two WireGuard interfaces: one through the forwarding service and one direct.

Interface naming:

a1 – local WireGuard over forwarding service (169.254.2.1, 192.168.51.3)

a2 – local direct WireGuard (169.254.2.2, 192.168.51.3)

c1 – remote WireGuard over forwarding service (169.254.2.3, 192.168.51.2)

c2 – remote direct WireGuard (169.254.2.4, 192.168.51.2)

Example configuration for a1:

[Interface]
PrivateKey = 
Address = 192.168.51.3/32
PostUp = ip route add 192.168.51.2/32 dev %i
Table = off
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0
Endpoint = <em>forwarding‑service:14967</em>
PersistentKeepalive = 10

a2 differs only in the endpoint IP/port.

[Interface]
PrivateKey = 
Address = 192.168.51.3/32
Table = off
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0
Endpoint = <em>remote‑C:14967</em>
PersistentKeepalive = 10

c1 and c2 are analogous, with c2 handling the direct path.

[Interface]
PrivateKey = 
ListenPort = 27002
Address = 192.168.51.2/32
PostUp = ip route add 192.168.51.3/32 dev %i # c2 config sets direct link
Table = off
[Peer]
PublicKey = 
AllowedIPs = 0.0.0.0/0

Static routes are then added:

# On local machine (outbound via a1)
ip route add 192.168.51.2/32 dev a1
# On remote machine (return direct via c2)
ip route add 192.168.51.3/32 dev c2

Ping test shows latency reduced from ~70 ms (via NTT) to ~30 ms, confirming the benefit.

Ping latency improvement
Ping latency improvement

Conclusion

While the three‑node solution works, it is somewhat cumbersome due to dummy interfaces and link‑local addressing. The two‑machine approach using a forwarding service achieves the same asymmetric routing with fewer hosts and better performance, making it a practical choice for scenarios where only the outbound path needs to be redirected.

static routingasymmetric routingwireguard
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.