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
4.86 star(s) 7 ratings

Latest reviews

Worked on the second try. When I noticed, there's a wg.public and a wg.private key. And a remote.public and a remote.private. And one of each is used in the local server configuration, while the other is used for the remote configuration.

So clearly marking the difference would make sense I think, it's easy to gloss over (public and private are not too dissimilar words, start with p, similar length).

Another thing that would maybe be useful is to present the allowed IPs option. If I understand correctly setting it to "AllowedIPs =" will only tunnel requests to connect to IPs from to and so on. Makes it useful to tunnel to your local network for file access and the like.

Four stars are there only because I'm a bit angry (at myself) for missing that key detail. Otherwise it's a simple and good guide.
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!