Register for the iXsystems Community to get an ad-free experience
Resource icon

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

[DRAFT] open to comments

  • 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 "Advance 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, Ctrx+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-specifig rules
$cmd 00099 reass all from any to any in       # reassamble 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

$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
# iocase 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

# iocase 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 3: 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 >>

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=

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

First release
Last update
5.00 star(s) 6 ratings

Latest reviews

Thank you great guide and works perfectly

A few things that took me a minute to realize:
- The "allow tun" checkbox is under "custom properties" now
- When port forwarding you use the WAN IP of the TrueNAS server (I am normally used to using the WAN IP of a particular jail or VM)
Great guide
Absolutely marvelous ! Nice How-To.
Super easy to follow, much quicker and better than trying to setup OpenVPN (even through the GUI). Thanks a million.
Great guide. It works perfectly. I use it on iOS when I'm out.
Works perfectly first time and so simple!