What They Don’t Tell You About Setting Up A WireGuard VPN
Tags: digitalocean, wireguard, tutorial, devops
Published: 2021-01-12 19:34:03



[WireGuard](https://www.wireguard.com/) 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](https://upload.wikimedia.org/wikipedia/commons/e/e8/VPN_overview-en.svg)

<figcaption>VPN connectivity overview. CC BY-SA 4.0, Image attribution: <a href="https://creativecommons.org/licenses/by-sa/4.0/legalcode">Creative Commons License</a></figcaption>

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](https://www.digitalocean.com/docs/droplets/how-to/create/). 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](https://www.digitalocean.com/docs/networking/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](https://en.wikipedia.org/wiki/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.
- [Enable droplet backups](https://www.digitalocean.com/docs/images/backups/how-to/enable/). 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](https://www.digitalocean.com/docs/images/snapshots/how-to/), then restore the snapshot to a new droplet. [Point a floating IP](https://www.digitalocean.com/docs/networking/floating-ips/how-to/create/) 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:
    - detect when the VPN server goes down and automatically switch (point a floating IP address) to a healthy standby using something like [Pacemaker](https://www.digitalocean.com/community/tutorials/how-to-create-a-high-availability-setup-with-corosync-pacemaker-and-floating-ips-on-ubuntu-14-04), or
    - 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](https://blog.cloudflare.com/warp-technical-challenges/).

# 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](https://www.wireguard.com/install/). For this walkthrough, I'll show instructions for Ubuntu 20.04, starting with installing the `wireguard` package:

```bash
sudo apt update
sudo apt install wireguard
```

The wireguard package installs two binaries:

- `wg` — 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. 🔑

```bash
# 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:

```bash
# /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](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) 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.
- `ListenPort = 51820` — 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!

```bash
# 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](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:

```bash
net.ipv4.ip_forward=1
```

Then apply the settings by running:

```bash
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.

![Network Packet Paths](https://i.ibb.co/pKrvrpG/Network-Packet-Paths.png)

<figcaption>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.</figcaption>

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

```bash
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](https://www.wireshark.org/), 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](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](https://superuser.com/questions/935969/what-is-masquerade-made-for/935988#935988). Assuming your VPN server is connected to the VPC on its `eth1` interface, you can enable masquerading on the VPN server with:

```bash
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.

![Network Packets Path A](https://i.ibb.co/9vK8pr3/Ping-Command-A.png)

<figcaption>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.</figcaption>

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:

```bash
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](https://www.businessinsider.com/trump-fcc-privacy-rules-repeal-explained-2017-4), all they'll see is an encrypted VPN connection.

![Network Packets Path B](https://i.ibb.co/Q81nLwv/Ping-Command-B.png)

<figcaption>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.</figcaption>

## 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](https://vmalli.com/managing-custom-iptables-rules-on-a-debian-docker-host/)), 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:

```bash
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](https://www.wireguard.com/install/) for non-Linux platforms):

```bash
sudo apt update
sudo apt install wireguard
```

Generate keys, similar to server setup:

```bash
# 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:

```bash
# /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.
- `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.
- `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.
- `[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.
- `PublicKey = ...` — 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.
- `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](https://www.wireguard.com/#cryptokey-routing).
- `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:

```bash
# /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:

```bash
wg-quick down wg0 && wg-quick up wg0
```

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

```bash
wg syncconf wg0 <(wg-quick strip wg0)
```

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

```bash
# 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](https://play.google.com/store/apps/details?id=com.wireguard.android). My efforts to run WireGuard under [crouton](https://github.com/dnschneid/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](https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md) 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](https://git.zx2c4.com/wireguard-go/about/).

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](https://openwrt.org/docs/guide-user/services/vpn/wireguard/start)), so you can route all those devices' outbound traffic through a VPN.

---

Thanks for reading! Hopefully, I’ve saved you time by passing on some of the insights and tips that I learned while digging deeper into the many facets of setting up a WireGuard VPN. If you have any suggestions or corrections, please let me know or [send us a tweet](https://www.twitter.com/tangramvision), and if you’re curious to learn more about how we improve perception sensors, visit us at [Tangram Vision](https://www.tangramvision.com/).

If you're setting up multiple VPNs or multiple VPN clients — or if you're interested in learning about infrastructure and configuration automation — check out the next tutorial I wrote: [Exploring Ansible via Setting Up a WireGuard VPN](https://www.tangramvision.com/blog/exploring-ansible-via-setting-up-a-wireguard-vpn).

# Corrections

- 2020-01-13: Previously, my explanation of what `AllowedIPs` does and how to route all traffic through the VPN was incomplete/misleading. Thanks to [Chris Siebenmann on Twitter](https://twitter.com/thatcks/status/1349439066048180226) for catching that!

# References

- [https://www.wireguard.com/install/](https://www.wireguard.com/install/)
- [https://www.wireguard.com/papers/wireguard.pdf](https://www.wireguard.com/papers/wireguard.pdf)
- [https://github.com/pirate/wireguard-docs](https://github.com/pirate/wireguard-docs#Address)
- [https://www.ckn.io/blog/2017/11/14/wireguard-vpn-typical-setup/](https://www.ckn.io/blog/2017/11/14/wireguard-vpn-typical-setup/)
- [https://stanislas.blog/2019/01/how-to-setup-vpn-server-wireguard-nat-ipv6/](https://stanislas.blog/2019/01/how-to-setup-vpn-server-wireguard-nat-ipv6/)