How-To: Setup a Wireguard VPN Server in a Jail

This resource was originally created by user: FreeVel on the TrueNAS Community Forums Archive. Please DM this account or comment in this thread to claim it.


  • To setup a VPN server based on the Wireguard technology and running from within a Jail.
  • The VPN server would allow remote devices to connect and access resources in the local network
  • All remote traffic should be routed via the VPN channel

Approach Overview

  • [1] The FreeNas host is running on the local network 192.x.x.x/24 using the bge0 iface
  • [2] The Jail is setup with VNET and network 172.x.x.x/24
  • [3] Since Wireguard will be running from within the Jail’s network (e.g. 10.x.x.x/32:51820) we’ll need to expose the Wireguard listening port outside the Jail (192.x.x.x:51820) , hence we need
    • NAT active on VNET
    • port-forwarding for 192.x.x.x:51820 (local) <–>172.x.x.x/24 (jail)
    • Firewall rules on Jail for 172.x.x.x:51820 (jail) ↔ 10.x.x.x:51820 (wireguard)
  • [4] Finally port-forwarding from WAN <—> 192.x.x.x:51820

Step 1: Setting up Jail with NAT and port-forwarding (local ↔ jail)

a. Go to Jail wizard and switch to “Advanced Mode”

b. Enable NAT, VNET and (optional) set default iface to your NIC, in my case: bg0

c. Under network properties set “NAT Port-forwarding” as shown. You can leave “NAT Interface” empty as in my case the jail picks bge0 as default at start-up

  • If bge0 has IP then your Jail wireguard server will listen on

d. Jail properties enable “allow_tun”

e. Save your jail config
Step 2: Install Wireguard

a. Enter your jail
# iocage console << your jail name >>

b. Setup “pkg” to upgrade against latest base

# pkg install nano 
# mkdir -p /usr/local/etc/pkg/repos 
# nano /usr/local/etc/pkg/repos/FreeBSD.conf

c. Paste the below setup, press Ctrl+X, and “Yes”

FreeBSD:  { 
  url: "pkg+${ABI}/latest",
  mirror_type: "srv",
  signature_type: "fingerprints",
  fingerprints: "/usr/share/keys/pkg",
  enabled: yes

d. upgrade your base to the latest
# pkg upgrade

e. install wireguard
# pkg install wireguard wireguard-go libqrencode
Step 3: Set up wireguard & Jail networking (jail ↔ wireguard)

a. Enable Wireguard iface, NAT & IP forwarding in “rc.conf”
# nano /etc/rc.conf

b. ensure the following lines exist in your rc.conf

# Enable Wireguard 

#Enable ip forwarding 
#Enable Firewall NAT in kernel mode 
#firewall_logging="YES" # Optional 

c. Create the ipfw.rules file
# nano /usr/local/etc/ipfw.rules

d. Paste the below lines into the file, Ctrl+X and “Yes”
Note: I have commented most rules out for the basic/simple config however feel free to uncomment and experiment if you need a more complex filtering.

# ipfw config/rules 
# from FBSD Handbook, rc.firewall, et. al. 

# Flush all rules before we begin. 
ipfw -q -f flush 

# Set rules command prefix 
cmd="ipfw -q add " 
# Internet-facing iface 
# Used for outboud NAT rules 
skip="skipto 1000" 

#### WG-specific Options #### 
# Listen Port 
# Subnet 

# Wireguard interface, matching the name in /etc/wireguard/*.conf 

# Allow NAT 
ipfw disable one_pass 
ipfw -q nat 1 config if $vif same_ports unreg_only reset
# allow all for localhost 
$cmd 00010 allow ip from any to any via lo0 
$cmd 00011 allow ip from any to any via $wg_iface

# NAT-specific rules 
$cmd 00099 reass all from any to any in          # reassemble inbound packets 
$cmd 00100 nat 1 ip from any to any in via $vif  # NAT any inbound packets 

# checks stateful rules. If marked as "keep-state" the packet has 
# already passed through filters and is "OK" without futher 
# rule matching 
$cmd 00101 check-state 

# allow WG 
#$cmd 00233 $skip udp from any to any src-port $wg_port out via $vif keep-state 
#$cmd 00234 $skip udp from $wg_subnet to any out via $vif keep-state 
#$cmd 00235 $skip tcp from $wg_subnet to any out via $vif setup keep-state
#$cmd 00320 $skip udp from any to any out via $vif keep-state 
#$cmd 00325 $skip tcp from any to any out via $vif setup keep-state #$cmd 00330 $skip icmp from any to any out via $vif keep-state
#$cmd 999 deny ip from any to any 

# NAT 
$cmd 1000 nat 1 ip from any to any out via $vif # skipto location for outbound stateful rules 
#$cmd 1001 allow ip from any to any

e. Using in-kernel NAT requires to disable TCP segmentation offloading (TSO). Set the following in Jail’s /etc/sysctl.conf


f. Restart the Jail & go get back to the console

# exit 
# iocage restart <<wireguard jail>> 

* Stopping wireguard 
  + Executing prestop OK 
  + Stopping services OK 
  + Tearing down VNET OK 
  + Removing devfs_ruleset: 10 OK 
  + Removing jail process OK 
  + Executing poststop OK 
wireguard: nat requires nat_interface, using bge0 
* Starting wireguard 
  + Started OK 
  + Using devfs_ruleset: 6 
  + Configuring VNET OK 
  + Using IP options: vnet 
  + Starting services OK 
  + Executing poststart OK
# iocage console <<wireguard jail>>

g. Confirm firewall settings loaded. Note: The above config of ipfw rules should look like this

# ipfw list 

00010 allow ip from any to any via lo0 
00011 allow ip from any to any via wg0 
00099 reass ip from any to any in 
00100 nat 1 ip from any to any in via epair0b 
00101 check-state :default 
01000 nat 1 ip from any to any out via epair0b 
65535 allow ip from any to any 

# ipfw nat show config 

ipfw nat 1 config if epair0b same_ports unreg_only reset

Step 4: Setup Wireguard Server & remote host configs

a. Create public/private key pairs for wireguard server and remote host

# cd /usr/local/etc/wireguard/ 
# wg genkey | tee wg.private | wg pubkey > wg.public 
# wg genkey | tee remote.private | wg pubkey > remote.public

b. Create Wireguard server config
# nano wg0.conf

c. Add the Server (interface) and remote (peer) by pasting the below config, then press Ctrl+X & “Yes”

Address = 
PrivateKey = << Paste wg.private here >> 
ListenPort = 51820 

PublicKey = << Paste remote.public here >> 
AllowedIPs =

d. Start Wireguard service

# service wireguard start
[#] wireguard-go wg0 
INFO: (wg0) 2020/07/09 22:35:10 Starting wireguard-go version 0.0.20200320 
[#] wg setconf wg0 /tmp/tmp.xw963cv4/sh-np.h2wle2 
[#] ifconfig wg0 inet alias 
[#] ifconfig wg0 mtu 1420 [#] ifconfig wg0 up 
[#] route -q -n add -inet -interface wg0 
[+] Backgrounding route monitor

Step 5: Setup wireguard on remote host

a. Create remote host config
# nano remote.conf

b. Add the following lines with the relevant values, press Ctrl+X and “yes”.

  • [Interface] address should match the [Peer] value in server’s wg.conf;
  • DNS entry can is optional
  • Allowed IPs can be limited to the remote subnet e.g
Address = 
PrivateKey = << your remote.private key here >> 
DNS = 

[Peer] PublicKey = << your wg.public key here >> 
AllowedIPs =,::/0 
Endpoint = << your WAN IP >>:<<WAN exposed port>>

as an example your remote conf could look like this

Address = 
PrivateKey = 8Ng/rzsuTlbZobnRqp2XC2bOgZP29P0K8d2Oa9m9pUA= 
DNS = 

PublicKey = bAUcryyyQ1x95zR27pMO7kwuskLpYM6fCOC211111gE= 
AllowedIPs =,::/0 
Endpoint =

c. Install wireguard on remote host and load the remote host config file using QR scan code (easier)

# qrencode -t ansiutf8 < remote.conf
Step 6: Setup port-forwarding on your router

a. Setup port-forwarding on your router so
<<WAN_IP>>:51820 <---> <<bge0_ip>>:51820
Step 7: Confirm VPN connectivity

a. Activate VPN connection from the remote host. You should be able to see something like this on your server if all setup correctly

# wg show 
interface: wg0 
   public key: bAUcnaxxxx95zR27pxxxkwuskLpYM6fCOC219hz1gE= 
   private key: (hidden) 
   listening port: 51820 
peer: ehISAoqcc1TuuuuuuuuoWGAr4yyyyyy5Oq8qU1i1SA= 
   allowed ips: 
   latest handshake: 13 hours, 45 minutes, 5 seconds ago 
   transfer: 87.82 MiB received, 1.69 GiB sent

[1] Building a WireGuard Jail with the FreeBSD's Standard Tools - genneko
[2] Chapter 33. Firewalls | FreeBSD Documentation Portal
[3] FreeBSD IPFW rules for WireGuard | SirToffski