Secure private network

Build a WireGuard Site-to-Site VPN

Two Debian gateways and two protected-LAN computers

By the OpenFactory Team· July 20, 2026 · Updated August 11, 2026

← Back to Blog

A site-to-site VPN joins two private subnets through dedicated gateways. Hosts on either side use ordinary LAN addresses; routing policy selects the traffic that crosses the encrypted tunnel.

WireGuard site-to-site packet pathA packet from Site A is routed to gateway A, selected by the Site B AllowedIPs entry, encrypted over UDP, decrypted by gateway B, and routed to Site B.Site A computer10.60.0.10Gateway Awg0: 10.252.20.1Peer: Site B public keyAllowedIPs: 10.70.0.0/24Gateway Bwg0: 10.252.20.2Peer: Site A public keyAllowedIPs: 10.60.0.0/24Site B computer10.70.0.10Encrypted UDP 51820No subnet NAT: LAN routes point each remote prefix at its local VPN gateway
AllowedIPs is doing two jobs: selecting the peer for outbound traffic and limiting which source addresses that peer may send after decryption.Read left to right: Site A sends the remote LAN prefix to gateway A. Gateway A selects gateway B from AllowedIPs, encrypts the packet, and sends it over UDP 51820. Gateway B authenticates the source prefix, decrypts the packet, and routes it to the Site B computer.

Design decisions to make first

Decide the addressing and routing contract before generating credentials. The two protected LANs and tunnel network must be distinct, each gateway needs a reachable endpoint, and every LAN router and firewall must know exactly which remote prefix is allowed across the tunnel.

  • The protected subnets must not overlap each other or the tunnel CIDR.
  • Each gateway needs a stable reachable IPv4 address or DNS name.
  • WAN interfaces must precede LAN interfaces so the generated mapping stays deterministic.
  • Routers on each LAN need a route to the remote subnet through the VPN gateway.
  • Firewalls must permit UDP 51820 between the public endpoints and the selected forwarding paths.

Deployable OpenFactory prompt

The prompt is a testable lab contract, not a production shortcut. It fixes every interface, subnet, selector, credential boundary, and assertion so the four-node topology can be evaluated consistently. Replace reserved WAN addresses and confirm every interface mapping before installing the private bundle.

Build and validate a Linux WireGuard site-to-site VPN named `branch-wireguard` as a BUILD_PLAN.

Create two `device_kind=linux-build` Debian Trixie gateways named `site-a-gateway` and `site-b-gateway`, each with the `headless`, `ssh`, and `vpn-wireguard` features plus tcpdump, iproute2, curl, and nftables. Site A has `172.31.220.10/24` on the shared `wan` network and `10.60.0.1/24` on `site-a-lan`. Site B has `172.31.220.20/24` on `wan` and `10.70.0.1/24` on `site-b-lan`. The WAN gateway is `172.31.220.1`, WAN is the only default-route network, and WAN must be the first interface on both gateways.

Create two additional `device_kind=linux-build` Debian Trixie computers with the `headless` and `ssh` features plus iproute2, iputils-ping, and curl. `site-a-computer` has one static interface, `10.60.0.10/24`, on `site-a-lan`; give it an `os.startup_scripts` oneshot named `remote-lan-route`, after `network-online.target`, that runs `ip route replace 10.70.0.0/24 via 10.60.0.1`. `site-b-computer` has one static interface, `10.70.0.10/24`, on `site-b-lan`; give it the same oneshot name, running `ip route replace 10.60.0.0/24 via 10.70.0.1`. Both computers must be separate recipes and topology nodes connected to their respective protected LANs.

Set `plan.vpn` to protocol `wireguard`, implementation `linux`, mode `site-to-site`, authentication `public-key`, tunnel CIDR `10.252.20.0/24`, listen port `51820`, and no clients. Site A's public endpoint is `172.31.220.10`, protected subnet is `10.60.0.0/24`, and connection type is `initiate`. Site B's public endpoint is `172.31.220.20`, protected subnet is `10.70.0.0/24`, and connection type is `respond`. Never put generated private keys in the BUILD_PLAN.

Add group assertions that `wg show wg0` succeeds on both gateways and that each peer configuration contains the other site's protected subnet. Add `command_output` assertions expecting `ready` that run `ip route get 10.70.0.10 | grep -q 'via 10.60.0.1' && ping -c 3 -W 5 10.70.0.10 >/dev/null && echo ready` on `site-a-computer`, and `ip route get 10.60.0.10 | grep -q 'via 10.70.0.1' && ping -c 3 -W 5 10.60.0.10 >/dev/null && echo ready` on `site-b-computer`. OpenFactory must generate distinct endpoint keys, apply both endpoint configs in the test VMs, prove traffic crosses between the two protected-LAN computers, and put the private deployment material in the owner-only bundle.

What OpenFactory builds

The topology uses two purpose-built Debian Trixie gateway images plus one Debian computer on each protected LAN. Each endpoint receives a unique private key, the remote public key, a tunnel address, and only the remote site's protected subnet in AllowedIPs. Credentials are generated after the plan is accepted, outside the language-model response, and stored in an owner-only deployment ZIP.

The registered group test is designed to boot both endpoints and both LAN computers on the authored WAN and LAN networks, apply each endpoint's private files, and run topology assertions. The assertions confirm wg0 exists on both gateways, each endpoint has the other site's route, and the two LAN computers can ping across the tunnel in both directions. A completed run and its managed-image identifiers, not this article alone, are the evidence that a specific image revision passed.

The routing detail that decides whether it works

Each peer's AllowedIPs contains the remote tunnel address and remote LAN prefix, never its own LAN. WireGuard uses those prefixes for outbound peer selection and inbound source validation. Ordinary LAN routes still direct the remote subnet toward the local VPN gateway.

This behavior is defined by WireGuard's official cryptokey-routing overview. The official quick start shows allowed-ips configuration and the wg show andwg showconf inspection commands.

The LAN routers still need routes for the opposite site. Do not add masquerading between the protected subnets to hide a missing route; it discards useful source identity and makes policy review harder. During validation, inspect wg show, then send traffic to the remote tunnel address and confirm the latest handshake and byte counters advance.

A 1420-byte tunnel MTU is a common starting point, not a universal value. For an IPv4 path, a 1392-byte payload asks the tunnel to carry a 1420-byte inner packet after the 28-byte IPv4 and ICMP headers. For IPv6, a 1372-byte payload reaches the same inner size after the 48-byte IPv6 and ICMPv6 headers. These examples use the Linux iputils implementation documented by the Linux ping(8) manual and its -M do path-MTU behavior.

# IPv4: run from each LAN toward the opposite LAN
ping -M do -s 1392 10.70.0.10

# IPv6: substitute a host on the opposite LAN
ping -6 -M do -s 1372 <remote-ipv6-host>

Run the probes from both LAN directions. If either reports that the message is too long, lower the payload until it passes consistently and set the interface MTU to the resulting inner-packet size. Then run a five-minute sustained TCP transfer in each direction. Define the acceptable retransmission threshold against a direct-path baseline before the test, require zero connection resets, and retain the command, endpoint conditions, tool version, and output with the completed run.

Production rollout

Treat the generated bundle as deployment input that still needs environment-specific review. Record the exact image and manifest, replace every lab endpoint, install only the credentials assigned to each site, constrain forwarding to declared prefixes, and prove recovery and rotation before carrying production traffic.

A completed result applies only to the recorded inputs. Any change to the image, manifest, endpoint, route, forwarding policy, credential, or relevant package invalidates that result and requires the full contract to run again.

  1. Download the private bundle and verify its manifest before moving files.
  2. Replace lab WAN addresses with the real endpoints and confirm interface names.
  3. Install only Site A files on Site A and only Site B files on Site B.
  4. Add LAN routes for the remote protected subnet, or make the VPN gateway the relevant LAN router.
  5. Open the required UDP ports and allow forwarding only between the declared selectors.
  6. Test routing in both directions, MTU-sensitive traffic, tunnel recovery, and key or certificate rotation.
Credential boundary: the deployment ZIP contains private keys. Keep it out of source control and shared ticket systems. Delete local working copies after controlled installation and rotate credentials after any suspected disclosure.

Compare this implementation with the Vyatta IKEv2/X.509 site-to-site guide, or use the road-warrior guide for individual roaming clients.

Choose the next validation step

Compare published self-service limits, or scope customer-controlled deployment and fleet requirements through a technical pilot.