> ## Content Index
> Fetch the complete content index at: https://corelab.tech/llms.txt
> Use this file to discover other available public pages before exploring further.

# Mastering WireGuard: Site-to-Site & Road Warrior Setups Behind OPNsense NAT
- URL: https://corelab.tech/mastering-wireguard-opnsense-docker-site-to-site/
- Published: 2026-01-20T14:15:23.000Z
- Updated: 2026-10-29T03:43:39.000Z
- Description: Stop running WireGuard on your firewall. Learn how to decouple your VPN using Docker or Linux VMs behind OPNsense for better performance, portability, and Site-to-Site routing.
- Author: Core Lab Joe
- Tags: Networking, Wireguard, OPNsense, Docker, Self-Hosting, #2026, #table-of-content

# Introduction: Why Decouple Your VPN?

Welcome back to the lab. If you’ve followed my [OPNsense series](https://corelab.tech/tag/opnsense/), you’ve built a fortress. You have [VLANs](https://corelab.tech/homelab-vlans-routing/), Intrusion Detection, and a [strict firewall ruleset](https://corelab.tech/opnsensept1/). But sometimes, you don't want your firewall to handle *everything*.

Maybe you want to run a cutting-edge version of WireGuard. Maybe you want to easily migrate your VPN server between hardware without touching your firewall config. Or maybe, like me, you just love Docker containers!

In this guide, we are going to do something advanced. We are going to punch a precise, secure hole in our OPNsense fortress to allow an internal **Linux Host** (running Docker or a VM) to act as our VPN Gateway.

We will cover two critical scenarios:

1. **The Road Warrior:** Your phone/laptop connecting securely back to your home LAN from a coffee shop.
2. **The Mesh Peer (Site-to-Site):** Connecting your lab to another network (like a friend's lab or a cloud VPS) so devices on both sides can talk to each other.

Let’s get to work.

---

# Part 1: The Docker Method 🐳

*For those who want lightweight, portable speed.*

### Prerequisites

- A Linux host running Docker & Docker Compose (Ubuntu/Debian recommended).
- [OPNsense](https://corelab.tech/opnsensept1/) running as your edge firewall.
- A static IP on your Docker host (e.g., `10.10.1.50`).

### Step 1: Prepare the Docker Host (Routing is Key!)

By default, Linux does not forward packets between interfaces. If you want to access other devices on your LAN (Scenario A) or bridge two networks (Scenario B), your host *must* act as a router.

1. SSH into your Linux/Docker host.
2. Edit the sysctl configuration:

```bash
# Create a persistent config file
sudo nano /etc/sysctl.d/99-wireguard.conf
```

1. Paste the following to enable forwarding:

```ini
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
```

![One small line for a config file, one giant leap for your packets.](https://corelab.tech/content/images/2026/01/Wireguard-sysctl-routing-enabled.webp)

**One small line for a config file, one giant leap for your packets.*

1. Apply the changes immediately:

```bash
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
```

⚡

Core Lab Tip: I also add in some optional "optimizations"!

If you'd like to give it a whirl, do this:

1. `sudo nano /etc/sysctl.d/wireguard-tuning.conf`
2. Paste this in the new file:

```bash
# 1. Enable BBR for better speed over internet
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# 2. Increase UDP Buffer sizes (Critical for WireGuard/Tailscale speed)
net.core.rmem_max = 7500000
net.core.wmem_max = 7500000

# 3. Optimize ARP cache (Since you are routing a subnet)
net.ipv4.neigh.default.gc_thresh1 = 512
net.ipv4.neigh.default.gc_thresh2 = 2048
net.ipv4.neigh.default.gc_thresh3 = 4096
```

![](https://corelab.tech/content/images/2026/01/Wireguard-tuning.webp)

---

### Step 2: The Docker Compose Stack (Easy way!)

We will use the excellent `linuxserver/wireguard` image. It’s stable, supports multiple architectures, and handles keys gracefully.

Create a folder for your project and a `docker-compose.yml`:

```yaml
services:
  wireguard:
    image: lscr.io/linuxserver/wireguard:latest
    container_name: wireguard
    cap_add:
      - NET_ADMIN
      - SYS_MODULE # Optional: Only if using host kernel modules
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - SERVERURL=vpn.corelab.tech # Your DDNS or Static WAN IP
      - SERVERPORT=51820
      - PEERS=3 # Number of client configs to generate (Phone, Laptop, Tablet)
      - PEERDNS=1.1.1.1 # Or use your PiHole/AdGuard IP (e.g. 10.10.1.5)
      - INTERNAL_SUBNET=10.13.13.0 # The VIP range for the VPN tunnel
      - ALLOWEDIPS=0.0.0.0/0 # For Road Warriors (Tunnel Everything)
    volumes:
      - ./config:/config
      - /lib/modules:/lib/modules # Optional: For kernel module access
    ports:
      - 51820:51820/udp
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped
```

Launch the stack:

```bash
docker compose up -d
```

![](https://corelab.tech/content/images/2026/01/Wireguard-docker-firing-up.webp)

Check the logs to see the QR codes generated for your peers!

```bash
docker compose logs -f wireguard
```

![](https://corelab.tech/content/images/2026/01/Wireguard-peer-QR-codes.webp)

Voila! Scan the QR codes with your client app on your phone!

- **Phone:** Scan the QR code from the terminal.
- **Laptop:** You can't scan a screen with the same screen. Navigate to `./config/peer1/` and copy the text from `peer1.conf` into your WireGuard client.

---

### Step 3: OPNsense Configuration (Opening the Gates)

Your container is listening, but your fortress (OPNsense) is currently blocking the door. We need to forward the traffic.

#### A. Port Forwarding (NAT)

1. Navigate to **Firewall -> NAT -> Port Forward**.
2. Click **Add (+)**.
  - **Interface:** WAN
  - **Protocol:** UDP
  - **Destination:** WAN address
  - **Destination Port Range:** 51820
  - **Redirect Target IP:** `10.10.1.50` (Your Docker Host IP)
  - **Redirect Target Port:** 51820
  - **Log:** Checked (Optional, recommended for initial troubleshooting)
  - **Filter Rule Association:** Add associated filter rule.
3. **Save** and **Apply**.

**⚠️ Verification:** Go to **Firewall -> Rules -> WAN** and ensure the rule created by the NAT wizard is present and enabled (Green play icon).

![](https://corelab.tech/content/images/2026/01/OPNsense-port-forward-wg1.webp)

![](https://corelab.tech/content/images/2026/01/OPNsense-port-fwd-wg2.webp)

**Telling OPNsense to pass the encrypted baton to our Docker container.*

At this point, **Scenario A (Road Warrior)** works! Your phone can connect, and because we enabled IP Forwarding on the host, you can browse your LAN.

---

### Step 4: Scenario B - Site-to-Site (The "Mesh" Config)

*Advanced Level: Connecting two labs!!!*

If this Docker container is connecting to a remote peer (e.g., a friend's network `192.168.99.0/24`) and you want your *entire* LAN to reach it, we need **Static Routes**.

1. **Edit `wg0.conf`:** Stop the container and edit `config/wg0.conf`. Add a peer block for the remote site:

```toml
[Peer]
# The Remote Site
PublicKey = <Remote_Public_Key>
Endpoint = remote.lab-friend.com:51820
# Allow traffic to the VPN IP AND the Remote LAN
AllowedIPs = 10.13.13.2/32, 192.168.99.0/24
PersistentKeepalive = 25
```

1. **Tell OPNsense the Path Exists:** OPNsense knows about your LAN (`10.10.1.0/24`), but it has no idea that `192.168.99.0/24` exists ***inside*** that Docker container.
  - **System -> Gateways -> Configuration:** Add a new Gateway.
    - **Name:** `Docker_WG_GW`
    - **Interface:** LAN
    - **IP Address:** `10.10.1.50` (Docker Host IP)

![](https://corelab.tech/content/images/2026/01/WG-VM-Gateway.webp)

Setting OPNsense up to route & allow your wireguard nets through the container host.

  - **System -> Routes -> Configuration:** Add a Static Route.
    - **Network:** `192.168.99.0/24` (The Remote Lab)
    - **Gateway:** `Docker_WG_GW`

![](https://corelab.tech/content/images/2026/01/Wireguard-packet-flow.webp)

**The "Return Trip" is usually where people fail. OPNsense needs a map (Static Route) to find the remote network.*

---

# Part 2: The Full VM Method 🖥️

*For those who want maximum kernel performance and cleaner routing.*

Sometimes Docker networking (NAT inside NAT) gets messy. Running WireGuard on a dedicated micro-VM (like a Proxmox LXC or a small Ubuntu VM) removes the Docker abstraction layer.

### Step 1: Install WireGuard

On your Ubuntu/Debian VM (`10.10.1.60`):

```bash
sudo apt update && sudo apt install wireguard resolvconf -y
```

Enable IP Forwarding just like we did in the Docker step (`sysctl.conf`).

### Step 2: Configure the Server

Create your config file:

```bash
sudo nano /etc/wireguard/wg0.conf
```

**The Config (Server Mode):**

```toml
[Interface]
Address = 10.13.13.1/24
ListenPort = 51820
PrivateKey = <Server_Private_Key>
# The Magic Masquerade Rule (Required for returning traffic!)
# This ensures that traffic leaving the VM appears to come from the VM's IP,
# solving routing issues for lazy clients.
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
# Road Warrior Client (Phone)
PublicKey = <Phone_Public_Key>
AllowedIPs = 10.13.13.2/32
```

![](https://corelab.tech/content/images/2026/01/wg0-config-for-the-server.webp)

Start it up:

```bash
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
```

### Step 3: OPNsense Integration

This is identical to the Docker method.

1. **Port Forward** UDP 51820 to the VM IP (`10.10.1.60`).
2. **Static Routes** (for Site-to-Site) pointing to the VM IP.

---

# Part 3: Performance Tweaks & "The Need for Speed" 🚀

Whether you use Docker or a VM, WireGuard is fast. But we can make it faster.

### 1\. MTU Tuning (The #1 Fix for "It connects but websites won't load")

When you wrap a packet inside a packet (VPN), it gets bigger. If it exceeds 1500 bytes, it fragments, causing massive slowdowns.

- **The Safe Bet:** Set `MTU = 1280` in your `[Interface]` block. This is the WireGuard minimum and works on almost every connection type (LTE, Starlink, Fiber).
- **The Optimized Bet:** Try `1420`.

### 2\. Userspace vs. Kernel Mode (Docker Specific)

By default, Docker containers run WireGuard in "Userspace" (using Go). This is fine for 100-500Mbps. If you have Gigabit fiber, you want **Kernel Mode**.

- **How:** Ensure your Docker Host has the `wireguard` module installed (`sudo apt install wireguard`).
- **Compose:** In your docker-compose.yml, ensure you have:

```yaml
volumes:
  - /lib/modules:/lib/modules
cap_add:
  - NET_ADMIN
  - SYS_MODULE
```

- The container will detect the host's kernel module and use it automatically, drastically reducing CPU usage!

![](https://corelab.tech/content/images/2026/01/wireguard-optimizations.webp)

**Kernel mode leaves Userspace in the dust for high-bandwidth applications.*

### 3\. Persistent Keepalive (NAT Survival)

If your connection drops silently after a few minutes of inactivity, your NAT router (or OPNsense) is closing the state.

- **Fix:** Add `PersistentKeepalive = 25` to your `[Peer]` configs. This sends a tiny "heartbeat" ping every 25 seconds to keep the firewall tunnel open.

---

# Conclusion

You have now successfully decoupled your VPN from your edge firewall. You can update, restart, or nuke your WireGuard instance without ever taking your main internet connection offline. You can also connect to a friend's network across the world with a secure, performant and private VPN, 100% of your own creation! #PowerOverwhelming!

Your OPNsense handles the security policy, and your Linux host handles the heavy cryptographic lifting.

> **This is the Way!**

**What's Next?** Now that you have a secure tunnel, why not use it to access your local LLMs remotely? Check out my guide on hosting[ local AI safely](https://corelab.tech/private-chatgpt-local-llm/)!

---

Now you can move on to enabling the "all seeing eye" of your LANs with Zenarmor, part 3 of the OPNsense series.

[OPNsense Layer-7 Control: A Deep Dive into Zenarmor (Part 3)In Part 1, we built the firewall (Layer 3/4). In Part 2, we hardened it with user accounts, 2FA, blocklists, and CrowdSec. Now, it’s time to add Layer 7 (Application) awareness. Your firewall is great at blocking IPs and ports, but it has no idea what is running inside![](https://corelab.tech/content/images/icon/Glowing_CPU-152.png)Core LabCore Lab Joe![](https://corelab.tech/content/images/thumbnail/zenarmor-logo-type-orange-background-2.png)](https://corelab.tech/opnsensept3/)

Or maybe keep things light and leave wireguard on OPNsense directly?

[The Speed of Light: OPNsense WireGuard Setup GuideMerry Christmas everyone! A little Christmas “treat” for all... You’ve built the fortress (OPNsense). Now you need a secure, invisible tunnel to get back inside when you’re on the road, travelling for work or on vacation! For many years, OpenVPN was the standard. But it’s slow, bloated,![](https://corelab.tech/content/images/icon/Glowing_CPU-153.png)Core LabCore Lab Joe![](https://corelab.tech/content/images/thumbnail/WGLightening-4.jpg)](https://corelab.tech/opnsense-wireguard-guide/)

---

## ❓ Frequently Asked Questions (FAQ)

### Why run WireGuard in Docker instead of directly on OPNsense?

Decoupling the VPN from the firewall lets you update, restart, or rebuild your WireGuard instance without ever taking your main internet connection offline, and makes it easy to migrate the VPN server to different hardware without touching your firewall config at all.

### What's the difference between the Road Warrior and Site-to-Site setups?

Road Warrior is your phone or laptop connecting back to your home LAN from somewhere else, like a coffee shop. Site-to-Site (Mesh Peer) connects two entire networks together, such as your lab and a friend's lab or a cloud VPS, so devices on both sides can reach each other.

### Why won't traffic route between my LAN and the WireGuard tunnel?

By default, Linux does not forward packets between interfaces. Your Docker host needs IP forwarding enabled explicitly, by setting net.ipv4.ip\_forward = 1 (and the IPv6 equivalent) in a sysctl config file and applying it, or nothing on the other side of the tunnel will be reachable.

### My WireGuard tunnel connects, but websites won't load. What's wrong?

This is almost always an MTU problem. Wrapping a packet inside a VPN packet makes it bigger, and if it exceeds 1500 bytes it fragments, causing exactly this symptom. Set MTU to 1280 in the \[Interface\] block as a safe baseline that works on nearly any connection type, or try 1420 for a more optimized value.

### Should I run WireGuard in userspace or kernel mode in Docker?

Userspace mode (the Docker default) is fine up to roughly 100-500Mbps. If you're on gigabit fiber, kernel mode is worth the extra setup: install the wireguard module on the Docker host, then mount /lib/modules and add the NET\_ADMIN and SYS\_MODULE capabilities in your Compose file so the container can use the host's kernel module directly, which drastically cuts CPU usage.

### Do I still need OPNsense if my Docker host is handling the VPN?

Yes. OPNsense still handles the security policy, the firewall ruleset, and deciding what's allowed to reach the Docker host in the first place. The Linux host is just doing the cryptographic heavy lifting for the tunnel itself, not replacing the firewall.