You’ve been there: the tunnel is “up,” the handshake is green, and you can ping the remote gateway with a satisfying 20ms response. You tell your boss or your client the site-to-site link is live. Then, the first user tries to open a 15MB Excel file from the remote SMB share, or a developer attempts a `git push`, and everything grinds to a halt. The connection doesn’t drop; it just… suffocates.
This is the “WireGuard Wall,” and it’s almost always caused by a fundamental misunderstanding of how WireGuard handles MTU and how OPNsense manages routing outside the encrypted envelope. In the legacy world of IPsec, we dealt with complex phase negotiations and brittle policy-based routing. WireGuard simplifies the crypto, but it shifts the burden of performance onto your ability to calculate overhead and manage the system routing table. If you’re seeing stalled SSH sessions or “Connection Reset by Peer” errors, you aren’t failing at networking; you’re likely just missing the specific nuance of how OPNsense integrates the WireGuard kernel module with its firewall state engine.
## Why You Must Use the WireGuard Kernel Module
In the early days of OPNsense WireGuard, we relied on a Go-based implementation (`wireguard-go`). It worked, but because it ran in “user space,” every packet had to be copied from the kernel to the application and back again. This context switching killed performance on lower-powered hardware and capped high-end systems.
Today, you should exclusively use `wireguard-kmod`. This moves the encryption and packet handling directly into the FreeBSD kernel. The result is a massive reduction in CPU cycles and latency. If you are building a site-to-site tunnel between two offices with Gigabit fiber, the kernel module is the difference between seeing 300Mbps and 940Mbps.
## The Actionable Playbook: Deploying the 1Gbps Tunnel
This playbook assumes you have OPNsense installed at two sites: Site A (Headquarters) and Site B (Branch).
### Step 1: Install and Enable the Kernel Module
Go to `System -> Firmware -> Plugins` and ensure `os-wireguard` is installed. By default, recent OPNsense versions use the kernel module if available. To verify, run `kldstat | grep if_wg` in the shell. If you see `if_wg.ko`, you are running at wire speed.
### Step 2: Configure the Local Instance (The “Vault”)
On Site A:
1. Navigate to `VPN -> WireGuard -> Instances`.
2. Create a new Instance. Give it a name like `S2S_Tunnel`.
3. Set the Listen Port (default 51820).
4. Click “Generate” for the Private Key. Copy the Public Key—you’ll need this for Site B.
5. Crucial: Set the MTU to `1420`. If Site A uses PPPoE (common in DSL/Fiber), set this to `1412`.
### Step 3: Define the Peer (The “Bridge”)
On Site A, add a Peer (Site B):
1. Public Key: Paste Site B’s public key.
2. Endpoint Address: The WAN IP or FQDN of Site B.
3. Endpoint Port: 51820.
4. AllowedIPs: This is the critical part. Enter Site B’s internal LAN subnet (e.g., `192.168.20.0/24`) AND the tunnel IP of Site B (e.g., `10.0.0.2/32`).
5. Keepalive: Set to `25`. This ensures the tunnel stays open through NAT firewalls.
### Step 4: The OPNsense Interface Assignment (The “Secret Sauce”)
Many tutorials stop after the WireGuard tab. This is why they fail.
1. Go to `Interfaces -> Assignments`.
2. Assign the new `wg0` device to a new interface. Name it `WAN_WG_S2S`.
3. Enable the interface, but leave “IPv4 Configuration Type” as None. We handle the IP inside the WireGuard Instance settings, but assigning the interface here allows OPNsense to treat it as a first-class citizen for firewall rules and routing.
### Step 5: Routing and MSS Clamping
Now we tell OPNsense how to actually reach Site B.
1. Create a Gateway: Go to `System -> Gateways -> Single`. Add a new Gateway. Select your `WAN_WG_S2S` interface. Set the IP to Site B’s tunnel IP (`10.0.0.2`).
2. Static Route: Go to `System -> Routes -> Configuration`. Add a route for Site B’s LAN (`192.168.20.0/24`) and point it to the Gateway you just created.
3. MSS Clamping: Go to `Firewall -> Settings -> Normalization`. Add a new rule.
* Interface: `WAN_WG_S2S`.
* Description: “Clamp MSS for WireGuard”.
* Max MSS: `1380`. (This is 1420 minus the 40-byte TCP/IP header).
### Step 6: Firewall Rules
On the `Firewall -> Rules -> WAN` tab, allow UDP 51820 from Site B’s WAN IP.
On the `Firewall -> Rules -> WAN_WG_S2S` tab (the interface we created), allow all traffic from Site B’s LAN to Site A’s LAN.
## Validating the Performance
Once the tunnel is up, don’t just ping. Use `iperf3`.
Run `iperf3 -s` on a machine at Site A and `iperf3 -c [IP] -t 30` from Site B. If you see consistent speeds near your ISP’s upload limit, you’ve succeeded. If the speed starts high and then drops or stalls, go back to Step 5 and verify your MSS clamping. 1380 is the “safe” number that resolves 99% of MTU issues.
### Frequently Asked Questions
Direct Practical Verdict:
## Why Does My WireGuard Tunnel Stall on Large Files?
The culprit is almost always IP fragmentation. Standard Ethernet frames are 1500 bytes. WireGuard adds a specific amount of overhead for its headers and authentication tags—exactly 60 bytes for IPv4 or 80 bytes for IPv6. When an application sends a 1500-byte packet, WireGuard wraps it, making it 1560 bytes. Your ISP’s router sees this oversized packet and, in a perfect world, fragments it. In the real world, many modern networks drop these packets or the “ICMP Fragmentation Needed” messages are blocked by over-zealous firewalls.
This is why your pings work (they are small) but your data transfers fail. In OPNsense, we solve this at two levels: the interface MTU and MSS (Maximum Segment Size) clamping. MSS clamping is the “magic wand” for site-to-site VPNs. It intercepts the TCP three-way handshake and forcibly lowers the announced segment size, ensuring the application never even tries to send a packet that would exceed the tunnel’s capacity.
## How Does Cryptokey Routing Differ from Legacy VPNs?
If you’re coming from an IPsec or OpenVPN background, the way WireGuard handles traffic feels alien. In IPsec, you define “Phase 2” entries—specific pairs of local and remote subnets. In OpenVPN, you often push routes from a server to a client.
WireGuard uses Cryptokey Routing. Each Peer configuration in OPNsense has an `AllowedIPs` field. This field serves two distinct purposes:
1. Outbound: It acts as an internal routing table for the WireGuard process. If a packet is sent to the WireGuard interface, the process looks at the destination IP and matches it against the `AllowedIPs` of its peers to decide which public key to use for encryption.
2. Inbound: It acts as a security ACL. If a packet arrives encrypted with “Peer A’s” key, but the internal source IP isn’t in “Peer A’s” `AllowedIPs` list, WireGuard drops it.
The “Costly Trap” here is assuming that putting a subnet in `AllowedIPs` is enough to make OPNsense route traffic there. It isn’t. `AllowedIPs` tells WireGuard what to do with traffic once it hits the interface, but you still need to tell the OPNsense kernel to send the traffic to that interface in the first place using the system routing table.
For a stable OPNsense site-to-site tunnel, use the `wireguard-kmod` for kernel-speed throughput. Set your MTU to 1420 (or 1412 for PPPoE) and—crucially—enable MSS Clamping on the WireGuard interface to 1380. Define remote subnets in “AllowedIPs” for the cryptokey handshake, but use OPNsense Gateways and Static Routes for actual traffic steering.
| Feature | WireGuard (kmod) | IPsec (IKEv2) | OpenVPN (DCO) |
|---|---|---|---|
| Routing Logic | Cryptokey Routing (AllowedIPs) | Policy-based or VTI | Push/Pull Routes |
| Standard MTU | 1420 Bytes | Variable (High Overhead) | 1500 (Fragmented) |
| Throughput | 1Gbps+ (Kernel-level) | High (Hardware Accel) | Moderate (DCO helps) |
| State Sync | Stateless (Fast Recovery) | Stateful (Re-keying) | Stateful (Session-based) |
Why can I ping the remote gateway but not access the web interface?
This is the classic MTU symptom. ICMP (ping) uses very small packets that fit under the 1500-byte limit even with VPN overhead. HTTP/HTTPS uses the maximum possible packet size. If your MTU/MSS isn’t clamped, the large HTTP packets are dropped, while pings pass through perfectly.Do I need Outbound NAT for a Site-to-Site tunnel?
Generally, no. In a true site-to-site setup, both sides have routes to each other’s internal subnets. You want the source IP to remain preserved so you can apply granular firewall rules. Only use Outbound NAT if you have overlapping subnets (which you should avoid) or if you are masking the branch office traffic to appear as the HQ’s IP.Can I use WireGuard site-to-site if one side has a dynamic IP?
Yes. The side with the dynamic IP should be the one to initiate the connection. In the configuration of the “Static” side, leave the “Endpoint Address” blank for the dynamic peer. The “Dynamic” side will check in, and WireGuard will update the endpoint address on the fly based on the incoming cryptographically signed packets.
Encrypted Site-to-Site Tunnels & MTU Clamping
Securing WAN interfaces against geographic scanning provides an airtight perimeter for encrypted cross-datacenter tunnels. For WireGuard site-to-site routing, read OPNsense GeoIP Blocking: MaxMind Country Rule Setup.

