Jun 3, 2020

Organizations spanning more than one site often use Virtual Private Networks (VPNs) to protect sensitive data sent from site to site. VPNs allow private networks to be extended across a public network. Most software VPN implementations utilize encryption to further protect data in transit.

Two of the most common VPNs, IPsec and OpenVPN, are long established and feature rich, but often difficult to set up and deploy. A while ago I attempted to set up a IPsec VPN on my home pfSense firewall, but gave up because of the complex authentication setup required.

VPNs are also useful for individuals wanting to increase privacy and security. Away from home you can easily and safely connect to services on your LAN as an alternative to opening them up to the internet. Additionally you can keep your internet use private while on public Wifi as all your traffic will be tunneled to your VPN server.

A key thing to remember is VPNs act as a tunnel. Even though your internet traffic will be inaccessible to those at your local coffee shop, a VPN by itself does not provide true privacy or anonymity. Internet traffic sent through a VPN can still be theoretically snooped at the other endpoint. If you you’re looking for robust privacy you should look into Tor or Tails.

WireGuard logo

WireGuard, developed by Jason Donenfeld, is a relative newcomer yo the VPN scene reaching a 1.0.0 release in March 2020. WireGuard aims to be simple and easy to use while also having a minimal attack surface, sound cryptography, and high performance.

  • Simple and Easy-to-use - WireGuard aims to be as easy to configure and deploy as SSH.

  • Minimal Attack Surface - WireGuard has a lean source code with just about 4,000 lines of code as opposed to OpenVPN at 70,000 lines of code. WireGuard is meant to be comprehensively reviewable by individuals.

  • Cryptographically Sound - WireGuard uses the latest cryptography and has received a third-party security audit.

  • High Performance - WireGuard’s fast cryptography and integration with the Linux Kernel means it can run circles around other VPN software.


WireGuard was incorporated into the 5.6 Linux Kernel and backported into the 5.4 Kernel (in time for the Ubuntu 20.04 release) making installation on Focal Fossa a breeze.

For this tutorial I will be setting up a WireGuard Ubuntu 20.04 container using lxd on a Ubuntu 20.04 server. The container will map the arbitrary port 51820 with the host server. This project requires either a static public ip address or dynamic DNS and the ability to forward ports on your router. I completed this tutorial with a pfSense router and dynamic DNS set up using Cloudflare’s free service with a purchased domain.

Create a new lxd container:

$ lxc launch ubuntu:20.04 wireguard
$ lxc exec wireguard -- /bin/bash

Install WireGuard and configure container firewall:

# apt update && apt upgrade -y
# apt install -y wireguard
# ufw allow 51820 && ufw enable


Generate as many keys as needed for the number of devices you will connect to your VPN.

Note: The specific keys used in this example have already been deleted and are not in use.

Generate WireGuard keys:

# cd /etc/wireguard && umask 077
# wg genkey | tee server.key | wg pubkey > server.pub
# wg genkey | tee peer_a.key | wg pubkey > peer_a.pub
# wg genkey | tee peer_b.key | wg pubkey > peer_b.pub

Keys needed for wg0.conf (copy your own keys generated in the previous step):

# cat server.key
# cat peer_a.pub
# cat peer_b.pub

Server Config

Server and client IP addresses have been chosen for simplicity. A third client would have IP address in the server config and in the client config.

# vim /etc/wireguard/wgo.conf
Address =
ListenPort = 51820
PrivateKey = SCURF86WoPr4rsOaKNbtfw9EVSsozu27AkXEHWBNtmM=

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# peer_a
PublicKey = GQaw2aYS4N5UNy1r4WWunVpjXU3YuEHwOsXWhsag/g8=
AllowedIPs =

# peer_b
PublicKey = EcVSOjJ7y6wVAZkwq20b8DBJzwUVqBFc2CjYU3XF0F4=
AllowedIPs =

Client Configs

In my case I am setting the DNS server of my clients to the IP address of my router. If your router has a DHCP server you can do the same or set client DNS to any other DNS server e.g. Cloudflare ( or Google (

Keys needed for peer_a.conf

# cat peer_a.key
# cat server.pub

Peer A config file:

# vim /etc/wireguard/peer_a.conf
Address =
PrivateKey = +DRT3xfc7Hkf9I+HX1tKPXje/uTn1hxHO0KFkvkncGk=

PublicKey = YR1hPdS+kkhgHh0sC9zNaat9oACAb6eKjlKHjxmd7k8=
AllowedIPs =, ::/0
Endpoint = wireguard.example.com:51820

Keys needed for peer_b.conf

# cat peer_b.key
# cat server.pub

Peer B config file:

# vim /etc/wireguard/peer_b.conf
Address =
PrivateKey = cPLzRaCjo95KV4lK8n7sVg67x5Pg5OA56qv+l0AHNnc=

PublicKey = YR1hPdS+kkhgHh0sC9zNaat9oACAb6eKjlKHjxmd7k8=
AllowedIPs =, ::/0
Endpoint = wireguard.example.com:51820

Enable and start WireGuard service

# systemctl enable --now wg-quick@wg0.service
# exit

Firewalls and Port Forwarding

Add a LXD proxy device to map ports between the host server and the wireguard container:

$ lxc config device add wireguard myport51820 proxy listen=udp: connect=udp:

Port forwarding is fairly straightforward in pfSense. First navigate to the pfSense web GUI. From the top menu select Firewall > NAT and click Add rule. Change Protocol to UDP, enter 51820 in the Destination port range line’s first Custom box, enter the host server’s IP address in the Redirect target IP line, and enter 51820 in the Redirect target port line’s Custom box.

pfSense port forwarding

Client test

I will be using an iPhone 7 as a client. The easiest method to transfer a WireGuard config file to a mobile device is by using a qrcode. You can either send the qrcode to straight to stdout or save it as a bitmap file.

$ lxc exec wireguard -- /bin/bash
# apt install -y qrencode
$ qrencode -t ansiutf8 -r peer_a.conf
$ qrencode -t png -r peer_a.conf -o peer_a.png
peer_a qr code

Install the WireGuard app for iOS and open it up. Tap Add a tunnel in the middle of the screen and then tap Create from QR code. Name the tunnel wg0 and enable it by toggling the on switch. If testing at home make sure to turn off Wifi and open up a browser to test the VPN connection. The public IP address on your client device should now match your home public IP address. You can navigate to a site like whatismyip to check.

You can also see stats from the wireguard container with the wg command.

# wg
interface: wg0
  public key: YR1hPdS+kkhgHh0sC9zNaat9oACAb6eKjlKHjxmd7k8=
  private key: (hidden)
  listening port: 51820

peer: GQaw2aYS4N5UNy1r4WWunVpjXU3YuEHwOsXWhsag/g8=
  allowed ips:
  latest handshake: 12 seconds ago
  transfer: 7.04 KiB received, 38.15 KiB sent

If you want to dive further into WireGuard make sure to check out the WireGuard docs, and the Arch Wiki is always is a great resource. If you’re having any issues getting your own VPN server set up, feel free to contact me via Twitter or Telegram.