Skip to main content
RunBook Academy

OPNsenseXXI · WireGuardWireGuard

WireGuard site-to-site — building tunnels between networks

Intermediate⏱ ~14 min🧪 Lab requiredwgifconfignetstatpingtcpdumppfctl

What you'll learn

  • Plan addressing for a WireGuard site-to-site tunnel — tunnel subnet, site subnets, and routing
  • Configure both endpoints of a WireGuard site-to-site tunnel on OPNsense
  • Write firewall rules that permit the tunnel traffic deliberately
  • Verify the bidirectional flow with handshake state and packet capture
  • Recognise the common site-to-site failure modes — endpoint mismatch, key distribution, AllowedIPs

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

A WireGuard site-to-site tunnel is the simplest of the production deployments: two firewalls, two subnets, one tunnel. But “simple” still has six configuration steps that all have to be right: key generation on both endpoints, public-key exchange, tunnel address assignment, AllowedIPs configuration, endpoint addresses, and firewall rules. The most common production failure is not a protocol error — it is a configuration step that was done in the wrong order, or with mismatched values between the two endpoints.

This lesson walks through a complete site-to-site tunnel build on OPNsense, from address planning to verification, with the diagnostic for each failure mode.

The reference topology

A typical site-to-site deployment has:

SiteFirewall public IPFirewall tunnel IPLAN subnet
Site A (local)198.51.100.110.99.0.1/3210.0.0.0/24
Site B (remote)203.0.113.110.99.0.2/3210.1.0.0/24

The two firewalls exchange public keys. Each firewall has one WireGuard instance (e.g. wg0). Each firewall has one peer entry pointing to the other. AllowedIPs on Site A’s peer entry is 10.1.0.0/24 (Site B’s LAN); on Site B’s peer entry is 10.0.0.0/24 (Site A’s LAN). The tunnel subnet 10.99.0.0/24 is the WireGuard-level addressing — the two endpoints communicate over this subnet, but it does not carry user traffic.

Step 1: generate key pairs on both firewalls

On each OPNsense firewall, generate a WireGuard key pair. The GUI is under VPN → WireGuard → Local. The operator clicks “Generate new key” — the firewall generates a Curve25519 key pair, stores the private key locally, and displays the public key.

The public key is what crosses to the other firewall. It is shared out-of-band — by reading it off the GUI, by copying it into an internal API, by an Ansible-driven configuration deployment. The private key never leaves the firewall.

SitePublic key
Site AsiteApublickey...=
Site BsiteBpublickey...=

Step 2: configure the WireGuard instance on both firewalls

On each firewall, create the WireGuard instance:

  • Listen port: 51820 (or any unused UDP port; 51820 is the WireGuard default).
  • Tunnel address: the /32 endpoint address (10.99.0.1/32 on Site A, 10.99.0.2/32 on Site B).
  • Disable routes? No — OPNsense should install routes for the peer’s AllowedIPs.

Step 3: add the peer on both firewalls

On each firewall, add the peer entry pointing to the other:

FieldSite A’s peer entry (for Site B)Site B’s peer entry (for Site A)
Public keysiteBpublickey...=siteApublickey...=
Endpoint203.0.113.1:51820198.51.100.1:51820
AllowedIPs10.1.0.0/24 (Site B LAN)10.0.0.0/24 (Site A LAN)
Persistent keepalive25 (recommended when one side is behind NAT)25

The PublicKey and AllowedIPs are mirrored: each side’s AllowedIPs is the other side’s LAN subnet. The endpoint is the remote firewall’s public IP. The persistent keepalive ensures NAT mappings stay alive.

Read-only / Safesite-to-site wg show
$ wg show wg0
interface: wg0
public key: siteApublickey...=
private key: (hidden)
listening port: 51820

peer: siteBpublickey...=
endpoint: 203.0.113.1:51820
allowed ips: 10.1.0.0/24
latest handshake: 4 seconds ago
transfer: 1.45 KiB received, 892 B sent

Illustrative output

Step 4: write the firewall rules

On each firewall, three rules are required:

  1. On the WAN interface: allow UDP 51820 from the remote firewall’s public IP.
  2. On the WireGuard interface: allow traffic from the remote LAN to the local LAN.
  3. On the LAN interface: allow traffic from the local LAN to the remote LAN.

For Site A (local LAN 10.0.0.0/24, remote LAN 10.1.0.0/24):

# WAN: allow the WireGuard transport from Site B's public IP
pass in on igb1 inet proto udp from 203.0.113.1 to 198.51.100.1 port 51820 keep state

# WireGuard interface: allow traffic from Site B to Site A
pass in on wg0 inet from 10.1.0.0/24 to 10.0.0.0/24 keep state

# LAN: allow traffic from Site A to Site B
pass in on lan inet from 10.0.0.0/24 to 10.1.0.0/24 keep state

For Site B, the same rules with mirrored values.

Step 5: verify the tunnel

The verification sequence:

  1. Inspect wg show on both firewalls. Each side should show the peer’s endpoint, a recent handshake timestamp, and non-zero transfer counters.
  2. Ping across the tunnel. From Site A, ping 10.99.0.2 (Site B’s tunnel address) confirms the WireGuard layer. Then ping 10.1.0.1 (Site B’s LAN gateway) confirms the inner routing.
  3. Capture on the WAN interface. tcpdump -ni igb1 'udp port 51820' should show UDP packets between the two public IPs. The packets are opaque (encrypted) but the endpoints and packet flow are visible.
  4. Capture on the WireGuard interface. tcpdump -ni wg0 should show the inner packets — the ping from Site A’s LAN to Site B’s LAN, decrypted by WireGuard.
Read-only / Safewireguard inner capture
$ tcpdump -ni wg0 -c 4 'host 10.1.0.1 and icmp'
12:34:56.789012 10.0.0.50 > 10.1.0.1: ICMP echo request, id 1234, seq 1, length 64
12:34:56.901234 10.1.0.1 > 10.0.0.50: ICMP echo reply, id 1234, seq 1, length 64
12:34:57.789012 10.0.0.50 > 10.1.0.1: ICMP echo request, id 1234, seq 2, length 64
12:34:57.901234 10.1.0.1 > 10.0.0.50: ICMP echo reply, id 1234, seq 2, length 64

Illustrative output

The capture on the WireGuard interface shows the inner traffic. The capture on the WAN interface shows the encrypted outer traffic. Both are correct; both confirm a working tunnel.

Step 6: handle NAT and policy routing

If the firewall is multi-WAN with policy routing, the LAN-to-any rule with a gateway group will match VPN traffic and try to send it through the WAN gateway. The fix is a before-rule on the LAN interface:

# On LAN: route VPN traffic via the default gateway (which honours the tunnel route)
pass in on lan inet from 10.0.0.0/24 to 10.1.0.0/24 keep state

The before-rule matches traffic destined for the remote LAN, has no gateway specified (so it uses the system default), and routes through the tunnel interface as expected. Position the before-rule above any LAN-to-any rule with a gateway group.

Common site-to-site failures

Five failures appear repeatedly:

  1. Keys not exchanged. Each side has the wrong public key for the peer. The handshake fails silently. The fix: verify both sides have the correct public keys (wg show wg0 on each).

  2. Endpoint wrong. The endpoint IP or port is incorrect. The firewall sends UDP to the wrong address and gets no response. The fix: verify the endpoint matches the remote firewall’s public IP and listening port.

  3. AllowedIPs overlap. Two peers with the same AllowedIPs create ambiguous routing. The fix: every peer’s AllowedIPs must be unique.

  4. No firewall rule on the WireGuard interface. Default deny blocks the traffic. The fix: add an explicit allow rule.

  5. Policy-routing rule steals the tunnel traffic. A multi-WAN LAN-to-any rule tries to send VPN traffic through the WAN gateway. The fix: add a before-rule for the remote LAN with gateway = default.

Summary

  • A WireGuard site-to-site tunnel has six configuration steps: key generation, instance creation, peer addition, firewall rules, verification, and NAT/policy-routing handling.
  • Each side’s AllowedIPs is the other side’s LAN subnet. The tunnel subnet uses /32 addresses for the endpoints.
  • Verification uses wg show (handshake and transfer state), ping (WireGuard layer and LAN layer), and packet capture on the WireGuard and WAN interfaces.
  • The persistent keepalive handles NAT traversal for endpoints behind NAT.
  • The common failures — key mismatch, endpoint wrong, overlapping AllowedIPs, missing rules, policy-routing theft — all surface as “tunnel is up but traffic does not flow”.

Knowledge check · 4 questions

  1. Q1. A WireGuard site-to-site tunnel between Site A and Site B shows no handshake on either side after 5 minutes. The configuration appears correct. What is the most likely cause?

  2. Q2. A WireGuard site-to-site tunnel with Site B behind NAT requires a persistent keepalive to keep the NAT mapping alive.

  3. Q3. Which of the following are valid steps in building a WireGuard site-to-site tunnel on OPNsense? Select all that apply.

  4. Q4. A multi-WAN OPNsense firewall has a LAN-to-any policy-routing rule that uses a gateway group. A WireGuard site-to-site tunnel is configured but no traffic flows from the LAN to the remote site. What is the most likely cause?

Passing score: 75%. Answers are checked in this browser.