What They Don’t Tell You About Setting Up A WireGuard VPN
By: Greg Schafer
Tags: DevOps, Wireguard, Tutorial, Digitalocean, VPN

WireGuard is a relatively new VPN implementation that was added to the Linux 5.6 kernel in 2020 and is faster and simpler than other popular VPN options like IPsec and OpenVPN.

We’ll walk through setting up an IPv4-only WireGuard VPN server on DigitalOcean, and I’ll highlight tips and tricks and educational asides that should help you build a deeper understanding and, ultimately, save you time compared to “just copy these code blocks” WireGuard tutorials.

Let’s get a server!

To set up a VPN, we need two computers that we want to connect. One of these is typically a desktop/laptop/phone in your possession. If you’re looking to remotely access company intranet sites and services, the other computer would be a server in an office or on a company cloud network. If you’re looking to remotely access your own home network, privately network with family/friends, or encrypt all of your internet traffic, then the other computer would be a personal server on a cloud provider like DigitalOcean or AWS.

VPN connectivity overview. CC BY-SA 4.0, Image attribution: https://en.wikipedia.org/wiki/File:VPN_overview-en.svg

For this walkthrough, we’ll use a new Ubuntu 20.04 server on DigitalOcean, though you could follow similar steps using any cloud provider. To create a new DigitalOcean server, follow their guide to creating a droplet. A “droplet” is the term DigitalOcean uses for a “server” or a “VM” or an “instance”.

VPCs and Private Networks

DigitalOcean servers are automatically created in a Virtual Private Cloud aka VPC (most cloud providers have VPC or private networking functionality), meaning they have an additional network interface ( eth1 in addition to eth0 ) and an additional private IP address. All servers, databases, and load balancers created in the same VPC can communicate with each other via their private IP addresses, which is a boost to security because all inbound traffic from the public internet (on eth0 ) can be blocked with a firewall.

You can use your VPN server as a sort of bastion host to access other resources inside your VPC using their private IP addresses. That is, your VPN server can route traffic to any IP address in the VPC and all the servers in your VPC can accept traffic only to their private IP addresses (to eth1 ), which protects those servers and the services they run from all sorts of attacks. The server configuration section below will mention how to set up this sort of architecture.

How can I keep my VPN server up?

Given the importance of VPN uptime — especially if it serves as the only way to access important servers in a VPC or remote company network — it’s worth considering how to handle or avoid downtime. There is a range of options and tradeoffs to consider, ordered below in increasing complexity/effort:

Do nothing! If you set up a server on DigitalOcean, install and configure the VPN, and take no further actions, then your VPN will go down when the server does. It’s not uncommon for DigitalOcean to migrate droplets between physical machines due to hardware issues, and the VPN will be unavailable if the migration can’t be performed without downtime. If a more serious issue causes downtime (e.g. accidental rm -rf / , networking misconfiguration, or a successful attack), then you'll need to set up and configure a new server from scratch to bring your VPN back up. If you didn't save the VPN server's private key offline, you'll need to generate a new private key and reconfigure all VPN clients to be able to connect to the new VPN server.

, networking misconfiguration, or a successful attack), then you'll need to set up and configure a new server from scratch to bring your VPN back up. If you didn't save the VPN server's private key offline, you'll need to generate a new private key and reconfigure all VPN clients to be able to connect to the new VPN server. Enable droplet backups. You can enable backups for an extra +20% of the droplet price, which will take weekly snapshots of the server. If the droplet ends up horribly broken or unresponsive, you can restore the latest backup and your VPN will be working again (in about 1 minute for a 1 GB droplet).

Set up manual failover. Set up the VPN server and take a snapshot, then restore the snapshot to a new droplet. Point a floating IP to one of the servers and use that IP address when connecting to the VPN. When the primary/active VPN server goes down for any reason, you can update the floating IP to point to the secondary/standby VPN server and your VPN will work again!

Set up automatic failover / high-availability. The next step up in sophistication is to either: (A) detect when the VPN server goes down and automatically switch (point a floating IP address) to a healthy standby using something like Pacemaker, or (B) put a UDP load balancer in front of multiple VPN servers, but… you might need some network trickery to allow multiple active VPN servers with the same IP address and you might also need sticky sessions, which breaks down for roaming clients without some protocol-level changes like Cloudflare made for WARP.

Set up a WireGuard server

With your shiny new server running, let’s install and configure WireGuard. For non-Linux platforms, follow the WireGuard website’s instructions and links. For this walkthrough, I’ll show instructions for Ubuntu 20.04, starting with installing the wireguard package:

sudo apt update

sudo apt install wireguard

The wireguard package installs two binaries:

wg — a tool for managing configuration of WireGuard interfaces

— a tool for managing configuration of WireGuard interfaces wg-quick — a convenience script for easily starting and stopping WireGuard interfaces

I encourage reading the manpages ( man wg and man wg-quick ), because they are concise, well-written, and contain a lot of information that is glossed over in most WireGuard tutorials!

To encrypt and decrypt packets, we need keys. 🔑

# Change to the root user

sudo -s # Make sure files created after this point are accessible only to the root user

umask 077 # Generate keys in /etc/wireguard

cd /etc/wireguard

wg genkey | tee privatekey | wg pubkey > publickey

Now we have a private key (which only the server should possess and know about) and a public key (which should be shared to all VPN clients that will connect to this server).

Next, create a configuration file at /etc/wireguard/wg0.conf .

If we use wg-quick (spoiler: we will) to start/stop the VPN interface, it will create the interface with wg0 as the name. You can create other interface config files with other names, such as wg1.conf , my-company-vpn.conf , or us_east_1.conf . The wg-quick script will create interfaces with names that match the config filename (minus the .conf part), as long as the name fits the regex tested in /usr/bin/wg-quick .

Print out your private key with cat /etc/wireguard/privatekey and then add the following to the configuration file:

# /etc/wireguard/wg0.conf on the server

[Interface]

Address = 10.0.0.1/24

ListenPort = 51820

# Use your own private key, from /etc/wireguard/privatekey

PrivateKey = WCzcoJZaxurBVM/wO1ogMZgg5O5W12ON94p38ci+zG4=

We’ll add the public keys of clients that are allowed to connect to the VPN later, but the above is all you need to run the VPN server for now. Here’s what it means:

Address = 10.0.0.1/24 — The server will have an IP address in the VPN of 10.0.0.1 . The /24 at the end of the IP address is a CIDR mask and means that the server will relay other traffic in the 10.0.0.1-10.0.0.254 range to peers in the VPN.

— The server will have an IP address in the VPN of . The at the end of the IP address is a CIDR mask and means that the server will relay other traffic in the range to peers in the VPN. ListenPort = 51820 — The port that WireGuard will listen to for inbound UDP packets.

— The port that WireGuard will listen to for inbound UDP packets. PrivateKey = ... — The private key of the VPN server, used for encryption/decryption.

At this point, you can start the VPN!

# This will run a few commands with "ip" and "wg" to

# create the interface and configure it

wg-quick up wg0 # To see the WireGuard-specific details of the interface

wg # To start the VPN on boot

systemctl enable wg-quick@wg0

Find more example commands for inspecting the interface at https://github.com/pirate/wireguard-docs#inspect.

Relaying traffic

Recall from above that Address = 10.0.0.1/24 means the server will relay traffic to peers in the subnet. That is, if you connect to the VPN and ping 10.0.0.14 (and a server exists on the VPN at that address), then your ping will go to the VPN server at 10.0.0.1 and be forwarded on to the machine at 10.0.0.14 . However, this won't work without one additional piece of configuration: IP Forwarding.

To enable IP Forwarding, open /etc/sysctl.conf and uncomment or add the line:

net.ipv4.ip_forward=1

Then apply the settings by running:

sysctl -p

Now, the VPN server should be able to relay traffic to other VPN hosts. From my understanding, running ping 10.0.0.14 will follow the left-to-right path shown in the diagram below. The diagram doesn't show the ping response from Peer C to Peer A, but you can mentally reverse all the arrows to see what the returning response path would look like.

The path of network packets from a ping command on Peer A to the destination server, Peer C. The packets enter the VPN at Peer A and route to the VPN server (Peer B), which relays the packets to Peer C via the VPN.

Troubleshooting relayed traffic

There are many places where something could go wrong, especially when relaying traffic between multiple servers as in the diagram above. When network requests are failing, tcpdump is a great tool for finding the source of failures and misconfigurations. If you wanted a complete view of the flow in the diagram above, you could run the following tcpdump commands on each machine:

sudo tcpdump -nn -i wg0

sudo tcpdump -nn -i eth0 udp and port 51820

Just be aware that clocks on servers might be slightly out-of-sync, so comparing timestamps in tcpdump output between servers could be misleading!

If you’re debugging network packets on a machine with a display like your desktop or laptop, you can use Wireshark, which is a graphical, user-friendly alternative to tcpdump .

For more insight into WireGuard itself, you can enable debug logging by following the instructions at https://www.wireguard.com/quickstart/#debug-info and then running tail -f /var/log/syslog to see the log messages.

Relaying traffic to a VPC or the internet

In addition to using a VPN server to relay traffic between VPN clients, you can use a VPN server as a way to access servers in a VPC (on DigitalOcean or AWS, for example) that are firewalled off from the public internet. This approach requires no change in WireGuard configuration on the server, but you will need to enable masquerading so that responses on one network (e.g. the VPC) can be mapped to the requesting machine on the other network (e.g. the VPN). If you’re unfamiliar with masquerading, check out this brief explanation. Assuming your VPN server is connected to the VPC on its eth1 interface, you can enable masquerading on the VPN server with:

iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth1 -j MASQUERADE

Now, a VPN client such as your laptop should be able to ping servers in the VPC, as in the diagram below.

The path of network packets from a ping command on Peer A to the destination server, Peer C. The packets enter the VPN at Peer A and route to the VPN server (Peer B), which terminates the VPN connection and relays the packets to Peer C via the VPC.

If you want to relay traffic through the VPN server to the internet (in which case, the VPN server is often labeled a bounce server), enable masquerading on the public-internet-facing interface (e.g. eth0 ) of the VPN server:

iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

Now, a VPN client such as your laptop can visit public internet sites via your VPN — if you’re on an unsecured coffeeshop wifi connection or you don’t trust your ISP, all they’ll see is an encrypted VPN connection.

The path of network packets from a ping command on Peer A to the destination server on the internet. The packets enter the VPN at Peer A and route to the VPN server (Peer B), which terminates the VPN connection and relays the packets over the public internet to the destination server.

Firewall rules

We’ve used iptables above for masquerading, but iptables is also important for managing the VPN server's firewall. You can use ufw instead, but learn and use iptables if you have the time — iptables is more foundational and powerful. Regardless of how you manage your firewall (I like this sort of approach), you'll need to:

allow UDP traffic to the WireGuard ListenPort (51820 in the sample server config above)

allow traffic forwarded to or from the WireGuard interface wg0

The iptables commands for those changes are:

iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT iptables -A FORWARD -i wg0 -j ACCEPT

iptables -A FORWARD -o wg0 -j ACCEPT

Many WireGuard tutorials suggest putting these iptables commands in the PostUp lines of the server WireGuard configuration, meaning the commands will be run when the wg0 interface is created. Be warned that, depending on how you manage your firewall, you may end up erasing these commands if you restart your firewall while the WireGuard interface is running, thereby making the VPN unreachable. Consider managing WireGuard firewall rules in the same place and with the same tool that you manage all your other firewall rules.

Set up a WireGuard client

Similar to the server setup, install WireGuard (follow the WireGuard website’s instructions and links for non-Linux platforms):

sudo apt update

sudo apt install wireguard

Generate keys, similar to server setup:

# Change to the root user

sudo -s # Make sure files created after this point are accessible only to the root user

umask 077 # Generate keys in /etc/wireguard

cd /etc/wireguard

wg genkey | tee privatekey | wg pubkey > publickey

Next, create a configuration file at /etc/wireguard/wg0.conf with the following content:

# /etc/wireguard/wg0.conf on the client

[Interface]

# The address your computer will use on the VPN

Address = 10.0.0.8/32 # Load your privatekey from file

PostUp = wg set %i private-key /etc/wireguard/privatekey

# Also ping the vpn server to ensure the tunnel is initialized

PostUp = ping -c1 10.0.0.1 [Peer]

# VPN server's wireguard public key (USE YOURS!)

PublicKey = CcZHeaO08z55/x3FXdsSGmOQvZG32SvHlrwHnsWlGTs= # Public IP address of your VPN server (USE YOURS!)

# Use the floating IP address if you created one for your VPN server

Endpoint = 123.123.123.123:51820 # 10.0.0.0/24 is the VPN subnet

AllowedIPs = 10.0.0.0/24 # To also accept and send traffic to a VPC subnet at 10.110.0.0/20

# AllowedIPs = 10.0.0.0/24,10.110.0.0/20 # To accept traffic from and send traffic to any IP address through the VPN

# AllowedIPs = 0.0.0.0/0 # To keep a connection open from the server to this client

# (Use if you're behind a NAT, e.g. on a home network, and

# want peers to be able to connect to you.)

# PersistentKeepalive = 25

There’s lots to talk about here!

Address = ... — Set the IP address of this client in the VPN. Packets sent to the VPN server with a destination of this address will be sent to whatever public IP address (endpoint) this client was last seen at.

— Set the IP address of this client in the VPN. Packets sent to the VPN server with a destination of this address will be sent to whatever public IP address (endpoint) this client was last seen at. PostUp = wg set %i private-key ... — Load the private key from the file after the wg0 interface is up. You can copy-paste the contents of the private key file into a PrivateKey line directly (as in the server config) if you prefer. I suggest not loading the private key via PostUp in the VPN server config however, because reloading the config (e.g. after adding a new client/peer) does not re-run PostUp commands, so the VPN will no longer know its private key and the VPN won't work as a result.

— Load the private key from the file after the interface is up. You can copy-paste the contents of the private key file into a line directly (as in the server config) if you prefer. I suggest loading the private key via in the VPN config however, because reloading the config (e.g. after adding a new client/peer) does not re-run commands, so the VPN will no longer know its private key and the VPN won't work as a result. PostUp = ping -c1 10.0.0.1 — Ping the VPN server after the wg0 interface is up to test that the VPN connection was successful. If the ping fails, wg-quick will take the interface back down. In my testing, sending traffic from the VPN server to the client didn't work until something was sent from the client to the server — sending 1 ping packet to the server with PostUp does the trick.

— Ping the VPN server after the interface is up to test that the VPN connection was successful. If the ping fails, will take the interface back down. In my testing, sending traffic from the VPN server to the client didn't work until something was sent from the client to the server — sending 1 ping packet to the server with does the trick. [Peer] — There can be multiple peer sections in the config, one for each VPN peer you wish to connect directly to. Often, the VPN server will be the only peer in a client's config file. Lines under the [Peer] header define how and where the client will connect to the peer.

— There can be multiple peer sections in the config, one for each VPN peer you wish to connect directly to. Often, the VPN server will be the only peer in a client's config file. Lines under the header define how and where the client will connect to the peer. PublicKey = ... — The public key of the VPN server.

— The public key of the VPN server. EndPoint = ... — The (usually publicly-accessible) IP address of your VPN server. This could be a floating IP address if you're using a cloud provider like DigitalOcean or AWS.

— The (usually publicly-accessible) IP address of your VPN server. This could be a floating IP address if you're using a cloud provider like DigitalOcean or AWS. AllowedIPs = ... — For incoming packets from the VPN server, their source IP address must match the addresses or ranges in AllowedIPs . For outgoing packets, the AllowedIPs is the mapping that tells WireGuard what peer (specifically their public key and endpoint) should be used when encrypting and sending. The last example ( AllowedIPs = 0.0.0.0/0 ) would enable WireGuard to send traffic destined for any IP address to the VPN server. With AllowedIPs = 0.0.0.0/0 , wg-quick up will conveniently run ip route and ip rule commands to route all your traffic through the VPN (useful in the aforementioned unsecured coffeeshop wifi or malicious ISP scenarios). For more info on how AllowedIPs works, check out WireGuard's documentation.

— For incoming packets from the VPN server, their source IP address must match the addresses or ranges in . For outgoing packets, the is the mapping that tells WireGuard what peer (specifically their public key and endpoint) should be used when encrypting and sending. The last example ( ) would enable WireGuard to send traffic destined for IP address to the VPN server. With , will conveniently run and commands to route all your traffic through the VPN (useful in the aforementioned unsecured coffeeshop wifi or malicious ISP scenarios). For more info on how works, check out WireGuard's documentation. PersistentKeepalive = 25 — Send a packet to the VPN server every 25 seconds, to ensure that the server can successfully route traffic to the client when the client doesn't have a public or stable IP address. Without this setting, the client can still send traffic to the VPN server and receive responses, but routers between the client and the server only keep their NAT/masquerade mapping for a few dozen seconds. After the mapping expires, the server won't be able to send anything to the client until the client sends something first. You typically won't enable this setting, unless you want to allow new connections from other devices on the VPN — for example, you would enable this on your home desktop if you wanted to connect to it from your laptop or phone while traveling.

Before starting the VPN on the client, the VPN server needs to be configured to allow connections from the client. Open /etc/wireguard/wg0.conf on the VPN server again and update the contents to match:

# /etc/wireguard/wg0.conf on the server

[Interface]

Address = 10.0.0.1/24

ListenPort = 51820

# Use your own private key, from /etc/wireguard/privatekey

PrivateKey = WCzcoJZaxurBVM/wO1ogMZgg5O5W12ON94p38ci+zG4= [Peer]

# VPN client's public key

PublicKey = lIINA9aXWqLzbkApDsg3cpQ3m4LnPS0OXogSasNW5RY=

# VPN client's IP address in the VPN

AllowedIPs = 10.0.0.8/32

The added [Peer] section enables the VPN server to coordinate encryption keys with the client and validate that traffic from and to the client is allowed. To apply these changes, you can restart the WireGuard interface on the server:

wg-quick down wg0 && wg-quick up wg0

If you want to avoid disrupting or dropping active VPN connections, reload the config with:

wg syncconf wg0 <(wg-quick strip wg0)

At this point, you can start the VPN on the client!

# This will run a few commands with "ip" and "wg" to

# create the interface and configure it

wg-quick up wg0 # To see the WireGuard-specific details of the interface

wg

Connecting from a Chromebook

If you’re connecting to a WireGuard VPN from a Chromebook, I suggest using the official Android WireGuard app. My efforts to run WireGuard under crouton failed, because crouton uses a chroot, so I was stuck with the Chromebook’s old Linux kernel (4.19) and unable to add kernel modules or network interfaces from within crouton. Similarly, crostini doesn’t allow updating or using custom kernel modules, but it does provide a great way to SSH into VPN-accessible servers while the Android WireGuard app is active.

Connecting from other devices

If you want to connect to a VPN from devices where you don’t have root access, you can try installing a userspace implementation of WireGuard such as wireguard-go.

If you want to connect to a VPN from devices you don’t control (e.g. smart TVs, IoT sensors), look into setting up WireGuard on your router (e.g. instructions for OpenWRT), so you can route all those devices’ outbound traffic through a VPN.