Configure WireGuard Site-to-Site VPN on Linux (2025 Guide)

Why WireGuard for a Site-to-Site VPN?

WireGuard has become a go-to VPN choice for modern Linux networks because it is fast, lightweight, and easier to audit than many legacy VPN stacks. For a site-to-site setup (connecting two networks, like HQ and a branch office), WireGuard works especially well: it uses simple public-key cryptography, keeps the configuration small, and performs efficiently even on modest hardware or small cloud VPS instances.

In this tutorial, you will build a reliable site-to-site WireGuard VPN between two Linux gateways. The steps are written for current distributions such as Ubuntu 22.04/24.04 or Debian 12, but the process is similar on most Linux systems. The goal is to route traffic between two private subnets securely, without exposing internal services to the public internet.

Network Example (Adjust to Your Environment)

This guide uses a clear example so you can map it to your own network. Site A (HQ) has LAN 192.168.10.0/24 and a Linux gateway with public IP A_PUBLIC_IP. Site B (Branch) has LAN 192.168.20.0/24 and a Linux gateway with public IP B_PUBLIC_IP. WireGuard will use a dedicated tunnel network: 10.99.0.0/24, where Site A will be 10.99.0.1 and Site B will be 10.99.0.2.

Prerequisites: UDP port access (commonly 51820), root or sudo privileges, and the gateways must be able to reach each other over the internet. Make sure you are not using overlapping LAN ranges (for example, both sides using 192.168.1.0/24). Overlapping subnets are a common reason site-to-site VPN routing fails.

Step 1: Install WireGuard

On both gateways, install WireGuard:

Ubuntu/Debian:

sudo apt update && sudo apt install -y wireguard

If you are using another distro, install the equivalent package (for example, on RHEL-based systems you may use EPEL or distro repositories depending on your version).

Step 2: Generate Keys (Both Sides)

WireGuard uses a private/public key pair per node. On Site A:

umask 077
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

Repeat the same on Site B. Then read the public keys so you can paste them into the peer configuration:

cat /etc/wireguard/publickey

Keep private keys private. Do not copy them into tickets, chat, or documentation.

Step 3: Create the WireGuard Config on Site A

Create /etc/wireguard/wg0.conf on Site A:

[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = SITE_A_PRIVATE_KEY
PostUp = sysctl -w net.ipv4.ip_forward=1; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT

[Peer]
PublicKey = SITE_B_PUBLIC_KEY
AllowedIPs = 10.99.0.2/32, 192.168.20.0/24
Endpoint = B_PUBLIC_IP:51820
PersistentKeepalive = 25

The key line for site-to-site routing is AllowedIPs. It tells Site A that traffic for the branch LAN (192.168.20.0/24) should be routed into the tunnel toward Site B.

Step 4: Create the WireGuard Config on Site B

Create /etc/wireguard/wg0.conf on Site B:

[Interface]
Address = 10.99.0.2/24
ListenPort = 51820
PrivateKey = SITE_B_PRIVATE_KEY
PostUp = sysctl -w net.ipv4.ip_forward=1; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT

[Peer]
PublicKey = SITE_A_PUBLIC_KEY
AllowedIPs = 10.99.0.1/32, 192.168.10.0/24
Endpoint = A_PUBLIC_IP:51820
PersistentKeepalive = 25

Step 5: Enable IP Forwarding Permanently

WireGuard can come up fine but routing will still fail if forwarding is disabled after reboot. On both gateways:

echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard-forward.conf
sudo sysctl --system

Step 6: Start and Enable the Tunnel

Bring up the tunnel on both sides:

sudo systemctl enable --now wg-quick@wg0

Check status and handshake:

sudo wg show

You should see a recent handshake time and data counters increasing when traffic flows.

Step 7: Firewall and Routing Checks

If you cannot reach the remote LAN, start with the basics: confirm UDP port 51820 is open on both public interfaces, and confirm that the LAN hosts use the Linux gateway as their default route (or have a route to the opposite LAN via the gateway). A very common issue is that the gateways can ping each other over the tunnel, but client machines cannot, because the clients do not know where to send the return traffic.

Test from the gateways first: ping Site B tunnel IP from Site A (ping 10.99.0.2) and then ping a host on the branch LAN. If gateway-to-gateway works but LAN-to-LAN fails, you likely need to add static routes on your LAN routers or ensure the gateways are the default routers for their subnets.

Step 8: Quick Troubleshooting Tips

No handshake: verify the endpoint IP/port, confirm public keys match the correct peers, and check upstream NAT or security groups. Handshake but no LAN access: confirm IP forwarding is enabled, confirm AllowedIPs includes the remote LAN, and confirm routes on LAN clients. Intermittent connectivity: keep PersistentKeepalive = 25 on at least one side when NAT is involved.

Once the tunnel is stable, you can harden it further by limiting which ports are allowed between sites, moving from iptables rules to a dedicated firewall policy, and documenting the exact subnets in AllowedIPs so the VPN stays predictable as your network grows.

Configure a WireGuard Site-to-Site VPN on Linux (Ubuntu/Debian) with Persistent Routing

Why WireGuard for a Site-to-Site VPN?

WireGuard has become one of the most practical VPN choices for modern Linux environments because it is fast, secure, and easy to troubleshoot. Unlike many older VPN stacks, WireGuard uses a small codebase and straightforward configuration files. In this tutorial, you will set up a site-to-site WireGuard VPN between two Linux servers (or gateways) so that two private networks can reach each other reliably, even after reboots.

Example scenario (adjust to your environment): Site A has LAN 10.10.0.0/24 and a Linux gateway with public IP A_PUBLIC. Site B has LAN 10.20.0.0/24 and a Linux gateway with public IP B_PUBLIC. WireGuard tunnel network will be 10.99.0.0/24, using 10.99.0.1 on Site A and 10.99.0.2 on Site B.

Prerequisites

You need root (or sudo) access on both gateways, outbound UDP allowed, and ideally a static public IP or stable DNS name for each side. This guide assumes Ubuntu/Debian, but the same concepts apply to other distributions. You should also confirm that each gateway can route traffic for its LAN (common when the gateway is also the LAN router, or when static routes exist on the LAN router pointing to the gateway).

Step 1: Install WireGuard

On both servers, install WireGuard tools:

Command:
sudo apt update && sudo apt install -y wireguard

Step 2: Generate Key Pairs

WireGuard uses public/private key pairs. Generate them on each gateway and store them with correct permissions:

On Site A:
umask 077
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

On Site B:
umask 077
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

Display each public key (you will paste it into the opposite side’s config):

Command:
cat /etc/wireguard/publickey

Step 3: Create the WireGuard Interface Config

WireGuard configurations live in /etc/wireguard/. Create wg0.conf on each site. Replace placeholders like A_PRIVATE_KEY, B_PUBLIC_KEY, and public IPs/DNS names.

Site A: /etc/wireguard/wg0.conf

[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = A_PRIVATE_KEY

[Peer]
PublicKey = B_PUBLIC_KEY
Endpoint = B_PUBLIC:51820
AllowedIPs = 10.99.0.2/32, 10.20.0.0/24
PersistentKeepalive = 25

Site B: /etc/wireguard/wg0.conf

[Interface]
Address = 10.99.0.2/24
ListenPort = 51820
PrivateKey = B_PRIVATE_KEY

[Peer]
PublicKey = A_PUBLIC_KEY
Endpoint = A_PUBLIC:51820
AllowedIPs = 10.99.0.1/32, 10.10.0.0/24
PersistentKeepalive = 25

The key detail for site-to-site routing is AllowedIPs. It tells WireGuard what networks to send through the tunnel. Here, each side includes the other site’s LAN (10.10.0.0/24 or 10.20.0.0/24) so packets are routed correctly.

Step 4: Enable IP Forwarding

If your gateways must pass traffic between LAN and VPN, Linux needs forwarding enabled. On both sites, run:

Command:
sudo sysctl -w net.ipv4.ip_forward=1

To make it persistent across reboots, edit /etc/sysctl.conf (or create a file under /etc/sysctl.d/) and ensure this line exists:

net.ipv4.ip_forward=1

Step 5: Adjust Firewall to Allow WireGuard UDP

WireGuard typically listens on UDP 51820. Allow it on both gateways. If you use UFW:

Command:
sudo ufw allow 51820/udp

If you rely on nftables/iptables, allow inbound UDP 51820 and ensure forwarding is permitted between your LAN interface and wg0. Firewall rules vary by environment, but the goal is consistent: UDP port open and forwarding allowed.

Step 6: Bring Up the Tunnel and Enable Autostart

Start the interface on both sides:

Command:
sudo wg-quick up wg0

Enable it at boot:

Command:
sudo systemctl enable wg-quick@wg0

Step 7: Test Connectivity and Routing

First, verify WireGuard handshake status:

Command:
sudo wg

You should see a recent “latest handshake” timestamp after traffic flows. Next, test the tunnel IPs:

From Site A:
ping -c 4 10.99.0.2

From Site B:
ping -c 4 10.99.0.1

Then test LAN-to-LAN reachability. For example, from a host on Site A LAN, ping a host on Site B LAN (or test from the gateway if it can reach the LAN):

Example:
ping -c 4 10.20.0.50

Common Problems (and Quick Fixes)

No handshake: confirm UDP 51820 is reachable from the internet, double-check Endpoint address/port, and ensure the correct public keys are pasted. A mismatched key is the fastest way to waste an hour.

Handshake works but LAN traffic fails: this is usually routing or firewall forwarding. Confirm IP forwarding is enabled and that your firewall allows forwarding between LAN and wg0. Also verify that each peer’s AllowedIPs includes the remote LAN subnet.

Remote LAN devices don’t know the return route: if your WireGuard box is not the default router for the LAN, you may need a static route on the LAN router (e.g., route 10.20.0.0/24 via the Site A WireGuard gateway IP, and vice versa).

Final Notes for a Stable Production Setup

For long-term reliability, keep configs simple and document your addressing plan. Consider using DNS names for Endpoints if IPs change, but make sure DNS is stable. Once everything works, capture the working configuration and back up /etc/wireguard/ securely, since private keys are sensitive. With the tunnel online, you can extend this design to multiple sites or add policy-based firewall rules to limit traffic between subnets.

Popular Posts

Install Ollama and Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install Ollama + Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install a Local AI Chatbot on Ubuntu 24.04 with Ollama and Open WebUI (Step-by-Step)

Trending Now

Recovering from Btrfs Boot Failures Using GUI Tools on Fedora

By the end of this guide the reader will be able to identify a Btrfs‑based Fedora installation, boot from a live USB, list and restore snapshots using the graphical utilities btrfs‑assistant and snapper, and verify that the system returns to a functional state without resorting to the command line. Understanding the Btrfs Layout Used by Fedora Fedora Workstation and Fedora KDE install the root filesystem as a single Btrfs partition that contains two default sub‑volumes. One sub‑volume holds the traditional “/” hierarchy, while the second is dedicated to /var/lib/machines . The latter exists to keep container images out of snapshot operations; it remains empty on systems that do not run virtual machines. Because Btrfs stores data in sub‑volumes rather than separate partitions, a snapshot captures the state of an entire sub‑volume at a point in time. The installer (Anaconda) automatically registers these sub‑volumes with the snapper service. Snapper maintains a series of read‑only ...