How to create a VPN on Ubuntu 20.04 using Wireguard

Wireguard is a modern and very easy to setup VPN available on multiple operating system. The application is available in the Ubuntu 20.04 official repositories, so it is also very easy to install. Unlike other software like OpenVPN which is based on the use of ssl certificates, Wireguard is based on the use of key-pairs. In this tutorial we will see how to configure, in few easy steps, a VPN server and a client peer on the latest stable version of Ubuntu.

In this tutorial you will learn:

  • How to install Wireguard on Ubuntu 20.04 Focal Fossa
  • How to create public and private key pair
  • How to configure the server and a client peer
  • How to redirect all incoming traffic to the VPN
Wireguard VPN on Ubuntu 20.04

Wireguard VPN on Ubuntu 20.04

Software Requirements and Conventions Used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Ubuntu 20.04 Focal Fossa
Software wireguard
Other Root privileges
Conventions # – requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux commands to be executed as a regular non-privileged user

Installation

Wireguard is officially available in the “universe” repository of Ubuntu 20.04, therefore we can install it via apt. The available version, at the moment of writing is 1.0.20200319-1ubuntu1:

$ sudo apt install wireguard

The system will ask us to confirm we want to install the program and its dependencies, and will complete the operation in a matter of seconds.

Generating keys

We must generate a public and a private key for each machine we want to use in our VPN. The private key should be kept secret on the machine, the public one is used to access the machine from the other peers.

To generate the keys we can use the wg utility. Inside the Wireguard configuration file we will need to reference the private key of the machine, while the public one will be used on the other peers. Notice that we will reference the keys directly, so theoretically we don’t need to store them to files. We will, however do it anyway, just for convenience.

To generate the private key for our server, we must use the genkey subcommand of wg. The command outputs the created key to stdout; to write the key to a file we can use the power of shell redirections:

$ wg genkey > server_private_key

The command will generate the key and store it to the server_private_key file, but will raise the following warning:

Warning: writing to world accessible file.
Consider setting the umask to 077 and trying again.

This is because with the default user umask (002) the files are created with mode 664, so are world-readable, which is not recommended. To solve this problem we can either change the umask used in the current shell session before creating the files:

$ umask 077

Or change the files permissions to 600 after creation. Here we will go for the latter solution.

Once our private key is ready, we can generate the public one which is based on it. To accomplish the task we use the pubkey subcommand of wg. Just as before we use shell redirections: first to pass the content of the server_private_key file to the stdin of the command, and then and to redirected the generated key to the server_public_key file:

$ wg pubkey < server_private_key > server_public_key

To spare some typing we can generate both keys, with just one command, which involves the use of the shell | (pipe) operator and the tee command:

$ wg genkey | tee server_private_key | wg pubkey > server_public_key

The output of the command on the left side of the pipe operator (|) is passed to the standard input of the program on its right side. The tee command, instead allow us to redirect the output of a command to both a file and to standard output ( more about shell redirections here).

Once our keys are ready we can create the server configuration file.

Server configuration file

To configure our Wireguard installation, we can create a configuration file called wg0.conf with the following content:

[Interface]
PrivateKey = <private key of the server (the content of the server_private_key file)>
Address = 10.0.0.1/24
ListenPort = 51820

Notice that the name of the file is arbitrary, but it should be based on the name we will use for our interface, wg0 in this case. This name will be referenced when starting the service, as we will see below.

In our example. the [interface] section of the configuration file contains the following fields:

  • PrivateKey
  • Address
  • ListenPort

The PrivateKey field value is nothing more than the server private key we generated earlier.

In the Address field we specified the address to assign to the interface in the VPN together with the subnet mask using the CIDR notation. In this case we used 10.0.0.1/24, so our Wireguard “server” address inside the VPN will be 10.0.0.1, which is in the available range of addresses that goes from 10.0.0.1 to 10.0.0.254.

Finally, in the ListenPort field, we specified what port Wireguard will listen on for incoming traffic. A rule to allow said traffic must also be added to our firewall. We will do this in the next section.

We can now change the permissions of the files and move them to the /etc/wireguard directory:

$ chmod 600 server_public_key server_private_key wg0.conf
$ sudo mv server_private_key server_public_key wg0.conf /etc/wireguard

We can now start the wg-quick service specifying the name of the Wireguard interface after @ in the unit name. What is this notation? It is a feature of systemd: with it we can generate multiple unit files on the base of a “template” one, passing the value that will be substituted in the template, after the @ symbol in the name of the unit. This is the content of the wg-quick@.service unit:

[Unit]
Description=WireGuard via wg-quick(8) for %I
After=network-online.target nss-lookup.target
Wants=network-online.target nss-lookup.target
Documentation=man:wg-quick(8)
Documentation=man:wg(8)
Documentation=https://www.wireguard.com/
Documentation=https://www.wireguard.com/quickstart/
Documentation=https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
Documentation=https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/wg-quick up %i
ExecStop=/usr/bin/wg-quick down %i
Environment=WG_ENDPOINT_RESOLUTION_RETRIES=infinity

[Install]
WantedBy=multi-user.target

The value we will specify after the @ in the unit name when starting or stopping it, will replace %i in the ExecStart and ExecStop lines. In this case we will use wg0:

$ sudo systemctl enable --now wg-quick@wg0

With the command above we started the service and also make so that is automatically started at boot. To verify our configuration has been applied we can run the wg command. The output produced should display information about the wg0 interface:

$ sudo wg
interface: wg0
  public key: nNx3Zpcv9D2dtgHDsoYGBNr64zG5jTJ4Z4T2sE759V4=
  private key: (hidden)
  listening port: 51820

Now, let’s proceed and configure our firewall and packet forwarding.

Firewall and network setup

In this tutorial I will assume the use of ufw. As we said before, we must add a rule to allow incoming traffic through the port we specified in the configuration file, 51820. We do it by running a very simple command:

$ sudo ufw allow 51820/udp

We also need to allow packet forwarding on our system. To accomplish the task we to remove the comment from line 28 of the /etc/sysctl.conf file, so that it looks like this:

# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1

To make the changes effective without rebooting the system, we need to run the following command:

$ sudo sysctl -p

In the next step we will configure the client.

Client key generation

Let’s now move on the system we want to use as a client. We need to install Wireguard on it; once done, we can generate a key-pair just as we did on the server:

$ wg genkey | tee client_private_key | wg pubkey > client_public_key

Just like we did server-side we create the wg0.conf configuration file. This time with this content:

[Interface]
PrivateKey = <client_private_key>
Address = 10.0.0.2/24

[Peer]
PublicKey = <server_public_key>
EndPoint  = <server_public_ip>:51820
AllowedIPs = 0.0.0.0/0

We already saw the meaning of the fields contained in the Interface section, when we generated the server configuration. Here we just adapted the values to our client (it will have the 10.0.0.2 address in the VPN).

In this configuration, we used a new section, [Peer]. In it, we can specify the information relative to a peer, in this case the one we use as a “server”. The fields we used are:

  • PublicKey
  • EndPoint
  • AllowedIPs

In the PublicKey field, we specify the public key of the peer, so, in this case, the public key we generated on the server.

The EndPoint is the public IP address or hostname of the peer followed by a colon and the port number the peer listens on (in our case 51820).

Finally, the value passed to the AllowedIPs field, is a comma-separated list of IP addresses and subnet mask with CIDR notation. Only traffic directed to the peer which comes from the specified addresses will be allowed. In this case we used 0.0.0.0/0 as a value: it works as a “catch-all” value, so all the traffic will be sent to the VPN peer (the server).

Just like we did server-side, we set the appropriate permissions and move the keys and the configuration file to the /etc/wireguard directory:

$ chmod 600 client_public_key client_private_key wg0.conf
$ sudo mv client_public_key client_private_key wg0.conf /etc/wireguard

With the configuration file in place, we can start the service:

$ sudo systemctl enable --now wg-quick@wg0

Finally, the [Peer] section relative to our client, must be added to the configuration file we previously created on the server. We append the following content to it:

[Peer]
PublicKey = <public key of the client>
AllowedIPs = 10.0.0.2/32

At this point we restart the service:

$ sudo systemctl restart wg-quick@wg0

The information about the associated peer should be now reported in the output of the wg command:

$ sudo wg
interface: wg0
  public key: nNx3Zpcv9D2dtgHDsoYGBNr64zG5jTJ4Z4T2sE759V4=
  private key: (hidden)
  listening port: 51820

peer: t5pKKg5/9fJKiU0lrNTahv6gvABcmCjQq5gF3BxwiDQ=
  allowed ips: 10.0.0.2/32

At this point, from the “client” system, we should be able to ping the server at the 10.0.0.1 address:

$ ping -c 3 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=2.82 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=38.0 ms
64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=3.02 ms

--- 10.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 2.819/14.613/37.999/16.536 ms

Conclusions

In this tutorial we saw how to create a VPN using Wireguard on the latest stable version of Ubuntu: 20.04 Focal Fossa. The software is really simple to install and configure, especially if compared to other solutions, as for example OpenVpn.

We saw how to generate the public and private keys used for our setup, and how to configure both the server and a client so that all traffic is redirected to the VPN. Following the given instruction you will have a working setup. For more information, please take a look at the project page.