Introduction
Physical leased lines can interconnect devices across multiple LANs (as if they were on the same LAN), but the complex process (ISP approval and deployment) makes them unfriendly for individual developers and small teams.
This article introduces how to build a WireGuard-based “cloud leased line” using WireGuard(R), achieving cross-LAN device interconnection at a low cost (no approval needed, only 60 CNY per year1).
Challenges
- LAN-level traffic forwarding. Any device in LAN A can access any device in LAN B (as if they were on the same LAN).
- The WireGuard node in each LAN is behind an ISP NAT device, with no NAT traversal support and no public IP.
- Only one WireGuard node runs in each LAN.
Scenario
A company has multiple offices (distributed across different cities), each with its own LAN hosting self-built private services (K8s, GitLab, CI).
- Developers at different offices need to access each other.
- Private services at different offices need to access each other.
- A developer at office A needs to access private services at office B.
- A developer at office A needs to receive callbacks from private services at office B.
Architecture

Approach
Assume there are two LANs: LAN1 and LAN2, with subnets 192.168.201.0/24 and 192.168.200.0/24 respectively. z (the letter before the IP) is a public server (with a public IP), while a and c are private servers (without public IPs).
The public server (z) and private servers (a and c) form a WireGuard network, and all three servers have IP forwarding enabled.
Device b (192.168.201.55) on LAN1 sends a request to device d (192.168.200.244) on LAN2. For example, running ping 192.168.200.244 on b. Both b and d are regular LAN devices, not WireGuard nodes.
Device b determines the next hop through the default gateway (192.168.201.1). The default gateway has a static routing rule configured (specifying that the next hop for 192.168.200.0/24 is a), so the default gateway sends the packet to a.
After the private server (a) receives the request, a’s kernel matches 192.168.200.0/24 via the system routing table to wg0, and the kernel sends the packet to wg0. After receiving the packet, WireGuard matches the destination address against its internal routing table implementation (WireGuard has its own routing table implementation based on a binary trie, where nodes are specified via the AllowedIPs configuration and mapped to Peers), determines that the target Peer for 192.168.200.0/24 is z, and forwards the packet to z.
After the public server (z) receives the request, z’s kernel matches 192.168.200.0/24 via the system routing table to wg0, and the kernel sends the packet to wg0. After receiving the packet, WireGuard matches the destination against its internal routing table, determines that the target Peer for 192.168.200.0/24 is c, and forwards the packet to c.
After the private server (c) receives the request, c’s kernel finds via the system routing table that 192.168.200.244/32 is not itself (192.168.200.248) but LAN device d. Since IP forwarding is enabled, c forwards the packet to device d.
Device d receives the request, processes it, and generates a response packet for b (192.168.201.55). Device d determines the next hop through the default gateway (192.168.200.1). The default gateway has a static routing rule configured (specifying that the next hop for 192.168.201.0/24 is c), so the default gateway sends the packet to c.
After the private server (c) receives the request, c’s kernel matches 192.168.201.0/24 via the system routing table to wg0, and the kernel sends the packet to wg0. After receiving the packet, WireGuard matches the destination against its internal routing table, determines that the target Peer for 192.168.201.0/24 is z, and forwards the packet to z.
After the public server (z) receives the request, z’s kernel matches 192.168.201.0/24 via the system routing table to wg0, and the kernel sends the packet to wg0. After receiving the packet, WireGuard matches the destination against its internal routing table, determines that the target Peer for 192.168.201.0/24 is a, and forwards the packet to a.
After the private server (a) receives the request, a’s kernel finds via the system routing table that 192.168.201.55/32 is not itself (192.168.201.247) but LAN device b. Since IP forwarding is enabled, a forwards the packet to device b.
Finally, device b on LAN1 receives the response packet from device d on LAN2.
Operations
Deploying the WireGuard Network
WireGuard is a Linux kernel module. Configuring a WireGuard network only requires adding two systemd configuration files and restarting systemd-networkd: systemctl restart systemd-networkd.
The command to generate key pairs is as follows (run the following command locally):
prikey="$(wg genkey)"; pubkey="$(wg pubkey <<< ${prikey})"; pskkey="$(wg genpsk)"
echo -e "PrivateKey: ${prikey}\nPublicKey: ${pubkey}\nPresharedKey: ${pskkey}"
The configurations for each server are as follows:
Public server (z) configuration:
File: /etc/systemd/network/wg0.netdev (WireGuard internal routing table rules are specified via AllowedIPs in this file)
[NetDev]
Name=wg0
Kind=wireguard
MTUBytes=1280
[WireGuard]
ListenPort=51820
PrivateKey=qJwN1zu5alr89c6k18U4SUwZXnUgVhr/tvSgLM7rmHQ=
[WireGuardPeer]
PublicKey=u4vkZywF9hZcy5QUhcbOCjD2EZ7GL2yXgoAH+IoIbHE=
PresharedKey=IBAsO/DwbautQjq/Gksv3lFJZwnUXvkMR3GyxeelS4o=
AllowedIPs=10.70.0.2/32
AllowedIPs=192.168.201.0/24
PersistentKeepalive=15
[WireGuardPeer]
PublicKey=/d8UR69JrdjxC2Fo9hvfZaoSC35LSGNLJ5mqjEyWjRo=
PresharedKey=YdpxayZdKMeoHzo9fVvBSWoDTn1c++r5awhm28Is9Ic=
AllowedIPs=10.70.0.3/32
AllowedIPs=192.168.200.0/24
PersistentKeepalive=15
File: /etc/systemd/network/wg0.network (system-level routing table rules are specified in this file)
[Match]
Name=wg0
[Network]
Address=10.70.0.1/16
[Route]
Destination=192.168.200.0/24
[Route]
Destination=192.168.201.0/24
Private server (a) configuration:
File: /etc/systemd/network/wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
MTUBytes=1280
[WireGuard]
ListenPort=51820
PrivateKey=gNCkDUdQ3yBynzEST9XOd31NsPluF3Q7aeFu82QsV04=
[WireGuardPeer]
PublicKey=CmmeC0yqofMMZhEGHuK5dd2Mxyxe7tA8wSniDWiI5V0=
PresharedKey=IBAsO/DwbautQjq/Gksv3lFJZwnUXvkMR3GyxeelS4o=
Endpoint=39.101.166.124:51820
AllowedIPs=10.70.0.0/16
AllowedIPs=192.168.200.0/24
PersistentKeepalive=15
File: /etc/systemd/network/wg0.network
[Match]
Name=wg0
[Network]
Address=10.70.0.2/16
[Route]
Destination=192.168.200.0/24
Private server (c) configuration:
File: /etc/systemd/network/wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
MTUBytes=1280
[WireGuard]
ListenPort=51820
PrivateKey=SF6RqNRo1c9vwFVteFonRwpbU1Ee1zsKp6v9c8pXwnA=
[WireGuardPeer]
PublicKey=CmmeC0yqofMMZhEGHuK5dd2Mxyxe7tA8wSniDWiI5V0=
PresharedKey=YdpxayZdKMeoHzo9fVvBSWoDTn1c++r5awhm28Is9Ic=
Endpoint=39.101.166.124:51820
AllowedIPs=10.70.0.0/16
AllowedIPs=192.168.201.0/24
PersistentKeepalive=15
File: /etc/systemd/network/wg0.network
[Match]
Name=wg0
[Network]
Address=10.70.0.3/16
[Route]
Destination=192.168.201.0/24
After adding the configurations and restarting systemd-networkd, the WireGuard network is ready.
Test NAT traversal on the public server z2:
# Test z accessing a
ping 10.70.0.2
# Test z accessing c
ping 10.70.0.3
IP Forwarding
To achieve LAN-level communication, we need to enable IP forwarding on the three servers a/c/z (WireGuard nodes). Run the following command on each server:
# The leading number in the sysctl config filename affects priority
# To verify the configuration is effective, run: systemd-analyze cat-config sysctl.d
echo -e 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/10-ip-forward.conf
sysctl -p /etc/sysctl.d/10-ip-forward.conf
Result:
Adding Static Routing Rules
Devices b and d are regular devices (not WireGuard nodes) in LAN1/LAN2 respectively, simulating regular users on the LAN.
To allow b to access d, we need to add the following static routing rule on the default gateway of b’s LAN. (My test environment’s gateway does not support static routing rules, so I added the routing rule directly on b):
ip route add 192.168.200.0/24 via 192.168.201.247
Similarly, add the following static routing rule on d’s LAN default gateway:
ip route add 192.168.201.0/24 via 192.168.200.248
Now all devices in LAN1/LAN2 can communicate with each other.
Testing
b’s IP address: 192.168.201.55, d’s IP address: 192.168.200.244
Test from b whether it can access d:
ping 192.168.200.244
Test from d whether it can access b:
ping 192.168.201.55
Final result:
The two LANs in this article are actually isolated through virtualization. My virtualization environment is in Guangzhou, while the public server (z) is in Beijing (Alibaba Cloud Zhangjiakou node, T6 instance with 2 vCPUs and 0.5 GB RAM, 300 CNY for 5 years, approximately 60 CNY per year). Due to the physical distance, the latency is relatively high. When purchasing a public server, it is best to choose a location between the two LANs.
Summary
This article introduced how to achieve multi-LAN interconnection using Linux IP forwarding and the WireGuard kernel module (without installing additional tools).
WireGuard, as a simple, secure, and high-performance solution, avoids the overhead of system calls through its kernel-level implementation (compared to userspace solutions like Tinc). Its UDP connectionless nature, the protocol’s statelessness3, forward secrecy, and DoS defense capabilities lay the foundation for secure cross-city communication.
For cost-conscious small and medium-sized enterprises, this is an excellent choice.
If you find any errors in this article, feel free to contact me via Email on the homepage. You can also find my WeChat QR code on the about page.
References
-
The cost here refers to the port fee of Alibaba Cloud Express Connect (starting from 2,000 CNY per year), excluding traffic fees. ↩︎
-
The public server z can proactively access private servers a and c via WireGuard IPs. a and c can also access each other. ↩︎
-
WireGuard is a UDP-based protocol. Only handshake requests signed by a node’s public key will be responded to by that node, preventing server probing (leaking the public key would allow an attacker to discover the node, making it probeable). This is the stateless property. See the WireGuard whitepaper. ↩︎