Skip to main content
RunBook Academy

← All labs in OPNsense

Lab · advanced · ~90 min

Lab: WireGuard site-to-site tunnel between two OPNsense firewalls

B · Nested virtualisationA · Physical hardware

Objectives

  • Plan addressing for a WireGuard site-to-site tunnel
  • Generate WireGuard keys on both endpoints and exchange public keys
  • Configure the WireGuard interface and peer on both endpoints
  • Write firewall rules that permit the tunnel traffic
  • Verify the bidirectional handshake and the inner traffic flow
  • Test the tunnel's behaviour when one endpoint is restarted

Prerequisites

This lab builds a WireGuard site-to-site tunnel between two OPNsense endpoints. One endpoint is the “left” site (Site-A), the other is the “right” site (Site-B). The tunnel is between the WAN addresses of the two firewalls and carries inner traffic between the LANs on each side.

The point of this lab is the discipline of addressing the three layers separately: the outer tunnel (the WAN endpoints and the UDP port), the inner tunnel address (the WireGuard interface on each side), and the inner routed traffic (the LANs that traverse the tunnel). The operator who confuses the layers builds a tunnel that connects but doesn’t route, or that routes but won’t pass traffic.

Objective

By the end of this lab you can:

  • Plan addresses for a WireGuard site-to-site tunnel.
  • Generate WireGuard keys on both endpoints and exchange public keys.
  • Configure the WireGuard interface and peer on both endpoints.
  • Write firewall rules that permit the tunnel traffic.
  • Verify the bidirectional handshake and the inner traffic flow.
  • Test the tunnel’s behaviour when one endpoint is restarted.

Requirements

  • Two OPNsense endpoints (physical or virtual) at different sites — Site-A and Site-B. The WAN addresses must be reachable from each other.
  • The os-wireguard plugin installed on both endpoints.
  • A LAN-side host on each side that can ping the other side’s LAN through the tunnel.
  • UDP port 51820 (default) reachable from Site-A’s WAN to Site-B’s WAN, and vice versa.

The lab can run with two nested OPNsense VMs with public addresses on the simulated WAN, or with two physical firewalls on different networks. The WireGuard handshake is the same.

Tasks

Task 1: Plan the addressing

Three layers, three sets of addresses:

LayerSite-ASite-B
WAN (outer)203.0.113.1/24198.51.100.1/24
Tunnel (inner)10.10.10.1/3210.10.10.2/32
LAN (inner)192.168.10.0/24192.168.20.0/24

The tunnel subnet is /32 per peer in the conventional WireGuard layout — each peer has a single inner address and routes the inner LANs through the tunnel. The /24 tunnel subnet (10.10.10.0/24) is used for clarity, but route statements point to the peer’s /32.

Task 2: Generate keys on both endpoints

On each endpoint, install the os-wireguard plugin if not already installed. Generate the keypair on each:

# On Site-A
wg genkey | tee /etc/wireguard/site-a-private.key | wg pubkey > /etc/wireguard/site-a-public.key
chmod 600 /etc/wireguard/site-a-private.key

# On Site-B
wg genkey | tee /etc/wireguard/site-b-private.key | wg pubkey > /etc/wireguard/site-b-public.key
chmod 600 /etc/wireguard/site-b-private.key

Or use the OPNsense GUI: VPN → WireGuard → Local → Generate new keypair. The GUI also stores the keys securely.

Exchange the public keys. The discipline: the private key never leaves the endpoint that generated it. The public key is on the other endpoint.

Task 3: Configure the WireGuard interface on Site-A

In the GUI: VPN → WireGuard → Local, add:

  • Name: wg_s2s
  • Listen port: 51820
  • Private key: Site-A’s private key
  • Tunnel address: 10.10.10.1/24

Save and apply.

Task 4: Configure the peer on Site-A

In the GUI: VPN → WireGuard → Peers, add:

  • Name: Site-B
  • Public key: Site-B’s public key
  • Endpoint: 198.51.100.1:51820 (Site-B’s WAN)
  • AllowedIPs: 10.10.10.2/32, 192.168.20.0/24
  • Persistent keepalive: 25 (every 25 seconds — useful for NAT traversal in production; in a lab with no NAT, 0 is fine)

The AllowedIPs is the source of the routing behaviour. WireGuard installs kernel routes for every entry in AllowedIPs pointing to the peer’s tunnel address. The 192.168.20.0/24 entry tells Site-A “to reach Site-B’s LAN, send via this peer”.

Save and apply.

Task 5: Configure the WireGuard interface on Site-B

The mirror of Site-A:

  • Local name: wg_s2s
  • Listen port: 51820
  • Private key: Site-B’s private key
  • Tunnel address: 10.10.10.2/24

Peers:

  • Name: Site-A
  • Public key: Site-A’s public key
  • Endpoint: 203.0.113.1:51820 (Site-A’s WAN)
  • AllowedIPs: 10.10.10.1/32, 192.168.10.0/24

Save and apply. The 192.168.10.0/24 entry tells Site-B “to reach Site-A’s LAN, send via this peer”.

Task 6: Verify the handshake

On either endpoint:

wg show

The output should show:

  • The local interface with its private key (redacted in show output) and tunnel address.
  • The peer with its public key, endpoint, and latest handshake.

The “latest handshake” timestamp is the truth. A handshake that is recent (within seconds) means the peers have exchanged UDP packets and the tunnel is alive. A handshake that is minutes old means the tunnel is dead or the peers have not exchanged packets yet.

# Trigger a handshake if needed
ping -c 3 10.10.10.2

The ping should produce a handshake on the next wg show.

Task 7: Write the firewall rules on both sides

The tunnel traffic must be permitted. WireGuard uses UDP 51820 for the handshake and re-key; the inner traffic is routed through the WireGuard interface.

On Site-A, in the GUI:

  • Firewall → Rules → WAN: allow UDP from 198.51.100.1 to 203.0.113.1 port 51820 (the WireGuard handshake).
  • Firewall → Rules → WireGuard: allow traffic from 192.168.10.0/24 to 192.168.20.0/24 and vice versa.

On Site-B, mirror the rules.

The WireGuard interface must have firewall rules. By default it has no allow rules; the inner traffic is dropped at the firewall even if the tunnel is up.

Task 8: Verify the inner traffic flow

From a LAN-side host on Site-A:

ping -c 5 192.168.20.1

The ping should reach the LAN gateway on Site-B. The WireGuard tunnel carries the packet, encrypted, from Site-A’s WAN to Site-B’s WAN, and the inner packet has source 192.168.10.x and destination 192.168.20.1.

Verify on the firewall:

# On Site-A
tcpdump -ni wg_s2s -c 5
# Should show the encrypted UDP packets

The tcpdump output shows the outer UDP packets on the WireGuard interface; the inner payload is encrypted and invisible.

Task 9: Capture the handshake with packet capture

On either endpoint, capture the WAN interface:

# Substitute your own value before running:
# WAN_IF is the WAN device as ifconfig(8) names it: em0, igb0, vtnet0 ...
WAN_IF=igb0

tcpdump -ni "$WAN_IF" 'udp port 51820' -c 10

The output should show the handshake packets (initial), keepalive packets (steady state), and re-key packets (every 2 minutes). The handshake is a single UDP exchange with the cookie reply; the steady state is a 1.0 packet heartbeat at the keepalive interval.

Task 10: Restart one endpoint and verify recovery

On Site-A, restart the WireGuard service:

configctl wireguard restart

The site-A’s wg interface comes back up. The peer configuration is still there. The handshake should resume within a few seconds — Site-A’s AllowedIPs will route Site-B’s traffic via the tunnel, and Site-B will see the handshake from Site-A’s WAN.

Verify:

# On Site-A
wg show | grep -A 5 'peer'

The “latest handshake” should be recent. The Site-B side should also show a recent handshake.

A tunnel that does not recover after a service restart is configured without a PersistentKeepalive. The PersistentKeepalive is the discipline that lets a peer behind a NAT re-establish the tunnel.

Task 11: Document the tunnel

WIREGUARD SITE-TO-SITE TUNNEL
=============================
Site-A WAN:    203.0.113.1
Site-A tunnel: 10.10.10.1/32
Site-A LAN:    192.168.10.0/24

Site-B WAN:    198.51.100.1
Site-B tunnel: 10.10.10.2/32
Site-B LAN:    192.168.20.0/24

UDP port:      51820

Verification:
- wg show shows recent handshake on both endpoints
- ping 192.168.20.1 from Site-A's LAN succeeds
- ping 192.168.10.1 from Site-B's LAN succeeds
- Restart of Site-A's WireGuard service: tunnel recovers
  within 5 seconds

Validation

  • Both endpoints have a WireGuard interface with a unique tunnel address.
  • Each endpoint has the other’s public key as a peer.
  • wg show shows a recent handshake on both endpoints.
  • Ping from Site-A’s LAN to Site-B’s LAN succeeds.
  • Ping from Site-B’s LAN to Site-A’s LAN succeeds.
  • The WireGuard firewall rules allow the inner traffic.
  • Restarting the WireGuard service on one endpoint gives the handshake within 10 seconds.

Expected Result

You have a working WireGuard site-to-site tunnel between two OPNsense endpoints. The two LANs can route through the tunnel; the WAN-side handshake is established; the inner traffic is encrypted. The tunnel survives a service restart on either endpoint.

Troubleshooting

  • No handshake. The public key is mismatched, or the endpoint address is wrong. Verify both ends with wg show. The peer row on the other side should show the matching public key.
  • Handshake works but inner traffic is blocked. The WireGuard firewall rules are missing. Add the allow rule on the WireGuard interface for the source/destination subnets.
  • Endpoint is behind NAT and the handshake dies. The Persistent keepalive is missing. Add a value of 25 seconds on the peer without a static endpoint.
  • ping works for the tunnel address but not for the LAN. The AllowedIPs on the remote side does not include the LAN subnet. Add it to the peer configuration on the side that is the source of the ping.

Cleanup

The WireGuard tunnel is the new baseline. Snapshot both endpoints:

# On Site-A
configctl backup download
# Save as opnsense-site-a-with-wireguard.xml

# On Site-B
configctl backup download
# Save as opnsense-site-b-with-wireguard.xml

To remove the tunnel:

# On both endpoints
wg-quick down wg_s2s
# Or in the GUI: VPN → WireGuard → disable the local; remove the peer

The restore path is the snapshot from before the lab.

What you learned

  • WireGuard site-to-site has three address layers: the WAN endpoint, the inner tunnel address, and the inner routed subnets. The AllowedIPs is the source of the routing.
  • The handshake is the truth — wg show’s “latest handshake” timestamp is the only health metric that matters.
  • The WireGuard firewall rules are required, not optional. The tunnel carries traffic; the firewall decides what is permitted.
  • A PersistentKeepalive is the discipline that lets a peer behind a NAT re-establish the tunnel after a restart.

Deliverables

  • · A working WireGuard tunnel between two OPNsense endpoints
  • · Ping between hosts on opposite LANs of the two endpoints
  • · A live capture showing the encrypted WireGuard UDP packets
  • · A documented handshake state showing the latest peer handshake
  • · A tunnel test procedure with restart validation

Verification status

Last reviewed
2026-08-14
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.