Creating your own VPN with Wireguard

December 6, 2023

A virtual private network, or VPN, is one of the most useful tools for self-hosting. The benefits of a VPN go way beyond just being able to access systems on separate networks, and setting one up with Wireguard is very easy!

Why you need a VPN

We have all been there. You set up your first homelab with some services like Nextcloud, piHole, and Bitwarden. Everything works perfectly, until you leave your home network. How do you continue to have access to your services regardless of where you are?

If your service provider allows it, you have the option of port-forwarding each service to the internet. This is usually not recommended for personal projects, as they are now open to access by anyone and exposed to attack. This is where a VPN comes in.

A VPN connects authorized devices together using one network. This network is “virtual” because the devices do not need to be physically connected, and “private” because traffic between devices is encrypted. If you have a VPS with a public IP address, you now have the ability to access your local network and all services running in it.

What is Wireguard?

When it comes to choosing a communication, or tunneling protocol for our VPN, there are many options. I chose to use Wireguard as it is extremely light-weight and fast. Wireguard is a communication protocol released in 2015 and is completely free and open-source. It was even incorporated into the Linux kernel, so you know it’s being taken seriously. It is highly extensible, so many third-party programs and scripts can easily use and build upon this protocol, but in this guide we will use the basic Wireguard protocol to build our VPN.

How does it work?

Wireguard works through a concept called Cryptokey routing. Essentially each client on the Wireguard VPN, called a peer, has a private key and list of peers it is connected to. Each peer in this list has a public key and IP addresses which get directed through the tunnel to that peer. The Wireguard server will have a list of all peers, and their unique, VPN-specific IP address on the Wireguard network, whereas the clients may just have the Wireguard server it’s peer list.

Now traffic from a client matching the IP address given under the server peer gets redirected via the Wireguard tunnel to the Wireguard server. The server decrypts and authenticates the traffic and routes it on to it’s destination. When returning traffic comes from the internet, it gets encrypted by the server and routed to the matching client.

Setting up a Wireguard server

The Wireguard server must be set up on a device that has a public IP address, like a VPS. This is the only way to allow on-demand connection to the Wireguard network from anywhere. Hetzner, Linode and many others offer fairly cheap cloud servers with public IP addresses. As with any other public-facing server, practice proper SSH security measures.

Now let’s set up Wireguard on the server.

  1. You can now install Wireguard on your server by following the instructions for your operating system.
  2. Enable packet forwarding on the server by editing /etc/sysctl.conf and uncommenting the line net.ipv4.ip_forward=1.
  3. Create a public-private key-pair in /etc/wireguard using: umask 077; wg genkey | tee privatekey | wg pubkey > publickey
  4. Create a Wireguard configuration file /etc/wireguard/wg0.conf and add the following:
# Server Wireguard Configuration
[Interface]

# VPN server IP address
Address = 10.0.0.1/24

# Port the VPN server should listen on
ListenPort = 51820

# Private key from /etc/wireguard/privatekey
PrivateKey = <SERVER-PRIVATE-KEY>
  1. Make sure the port specified under ListenPort is open in the server’s firewall.
  2. Enable the start of Wireguard on boot with sudo systemctl enable wg-quick@wg0
  3. Start the server now with sudo systemctl start wg-quick@wg0
  4. Check that Wireguard is running with wg. This is also a useful command to seeing the status of the Wireguard network.

That’s pretty much all when it comes to setting up the Wireguard server! Now we will add some clients to the network.

Adding clients to the VPN

A Wireguard client can be any device from a smartphone to a router. The procedure is very similar to setting up a Wireguard server.

  1. Install Wireguard on the device by following the instructions for your operating system.
  2. Create a public-private key-pair in /etc/wireguard with umask 077; wg genkey | tee privatekey | wg pubkey > publickey
  3. Create a Wireguard configuration file /etc/wireguard/wg0.conf and add the following:
# Client Wireguard Configuration
[Interface]

# VPN client IP address
Address = 10.0.0.2/24

# Private key of the client from /etc/wireguard/privatekey
PrivateKey = <CLIENT-PRIVATE-KEY>


[Peer]

# Public key of the Wireguard server
PublicKey = <SERVER-PUBLIC-KEY>

# IP addresses to be routed through the tunnel to the server
AllowedIPs = 0.0.0.0/0

# The public IP address (or domain name) and listen port of the Wireguard server
Endpoint = 172.100.110.120:51820
  1. Enable the start of Wireguard on boot with sudo systemctl enable wg-quick@wg0
  2. Start the server now with sudo systemctl start wg-quick@wg0
  3. Check that Wireguard is running with wg. This is also a useful command to seeing the status of the Wireguard network.

Now the server needs to be set up with the new client.

On the Wireguard server:

  1. Stop Wireguard with: sudo systemctl stop wg-quick@wg0
  2. Edit /etc/wireguard/wg0.conf and add the client:
# Server Wireguard Configuration
[Interface]
...

[Peer]
# Public key of the client
PublicKey = <SERVER-PUBLIC-KEY>

# IP addresses to be routed to the client (VPN IP of client)
AllowedIPs = 10.0.0.2/32
  1. Start the Wireguard server again: sudo systemctl start wg-quick@wg0
  2. Verify connection from the server to the client with ping 10.0.0.2
  3. Verify connection from the client to the server with ping 10.0.0.1 on the client side.

This is the basic setup of a VPN using Wireguard, but there are still some other settings to consider to make the VPN as useful as possible.

Keepalive

A problem arises when a client is behind a NAT or firewall, which the primary setup of a homelab, for example. This client will eventually disconnect from the Wireguard server if no traffic is flowing. The server, alongside other clients on the network, are then no longer able to reach the client.

To keep the connection between the client and the server up, a “keep-alive” packet needs to be sent to the server. This is done by editing the configuration of clients /etc/wireguard/wg0.conf and adding PersistentKeepalive = 25 to the server peer:

# Client Wireguard Configuration
[Interface]
...

# Server peer
[Peer]
...

# Keep server-client connection alive
PersistentKeepalive = 25

This should only be done with clients that remain on local networks which need to be accessed from outside of the local network, like a home server. It does not need to be done on “mobile” clients, like phones and laptops, as a connection to these devices is not necessary.

Exposing local networks

Another extremely useful use-case of a VPN is exposing an entire local network to the VPN. This allows you to access devices in a local network without them being directly connected to the Wireguard VPN. For this, a Wireguard “gateway” is needed, which redirects VPN traffic to the local network. This device can be the actual gateway of the local network, like the router, or any other device on the local network, as long as it can run Wireguard.

Let’s first set up the server configuration. For the peer located in the local network you would like to access, add the IP address block to the AllowedIPs. In this example, the local subnet is 192.168.8.0/24.

# Server Wireguard Configuration
[Interface]
...

# Peer in local network to expose
[Peer]
...
AllowedIPs = 10.0.0.2/32, 192.168.8.0/24

This allows all traffic destined to a device in the local subnet (192.168.8.0/24) to be routed to that peer.

Now we need the client located in the local subnet to route the Wireguard traffic over its network interface to the device on the local network. Edit the Wireguard config for the client to enable the iptables routing:

# Client Wireguard Configuration
[Interface]
...
PreUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADEACCEPT
...

This enables forwarding in the kernel and sets up the forwarding of wg0 to eth0 traffic and visa versa. Make sure the network interface name matches the one connected to the local network. If the client is connected via WiFi this may be wlan0 instead of eth0

Now, any client in the Wireguard VPN with either all traffic forwarded through the Wireguard server (AllowedIPs = 0.0.0.0/0) or traffic to the local subnet (AllowedIPs = 192.168.8.0/24, ...), has access to any device on the local subnet.

VPN-wide DNS

If you have a self-hosted DNS resolver and ad-blocker like piHole, you can use this for all devices on the Wireguard VPN. This enables piHole’s ad-blocking as well as local DNS resolution for your devices from any device in the VPN.

To set the DNS server for a client just add DNS = IP-OF-LOCAL-DNS to the [Interfaces] part of the client Wireguard config. If you have a DNS server running on the same device as the Wireguard server (10.0.0.1), the configuration for any client would look as follows:

# Client Wireguard Configuration
[Interface]
...
DNS = 10.0.0.1

Tip: You can set up local domain names for any device on any local subnet using piHole and never need to remember an IP address again! Regardless of the device’s network!

Selective tunneling

Let’s say you only want traffic going to your local network to be forwarded through the Wireguard VPN. This essentially keep all “normal” internet traffic unencrypted and off your Wireguard server, except for when you want to access a device on your local network.

All you need to do is edit the AllowedIPs section going to the Wireguard server in your client configuration. If you only want to access your local network (192.168.8.0) when you’re away from home, the client configuration would look like this:

# Client Wireguard Configuration
[Interface]
...

# Wireguard server peer
[Peer]
...
AllowedIPs = 192.168.8.0/24

If you have multiple local networks to access, you can add them here. You can also have two Wireguard interfaces, one forwarding all traffic through your Wireguard server (for save browsing on public WiFi networks) and a second one for simply accessing local networks.

Site-to-site

A site-to-site connection, allows any device on one local network to talk to any device on a separate local network. It only requires one device on each network with Wireguard which are connected to a common Wireguard server. These devices will be the “Wireguard Gateways”. Each device should forward traffic destined for the other local network via Wireguard by adding the remote subnet to AllowedIPs in the Wireguard configuration file.

Now you need to add a route to the routing table on your local network gateway (router) to route all traffic destined for the remote local network to the Wireguard Gateway. For FritzBox this can be set under “Home Network” > “Network” > “Network Settings” > “IPv4 Routes” > “New IPv4 Route”. Under “IPv4 Network” add the subnet of the remote network with matching subnet mask (usually 255.255.255.0). Then add the IP address of the Wireguard gateway device on the local network under “Gateway”. Repeat this for the remote network to allow bi-directional site-to-site access.

On the remote network, make sure the local network is exposed to the Wireguard gateway by following this part of the post.

Example Wireguard VPN setup

Ok, so there is a lot you can do with Wireguard, and it might be a little confusing at first. Let’s visualize a typical VPN setup and the corresponding Wireguard configurations to incorporate the features we discussed above.

In this example, we have a Wireguard server with a public IP address, two local networks, each with a Wireguard client, and two mobile devices (laptop and smartphone). The server in “Local Network 1” has a DNS server with domain names for various devices on Local Network 1 and 2.

Example Network (created with: draw.io)

We want our mobile clients to be able to access both local networks but do not want all traffic running through the Wireguard server. This is how we can set up our clients:

Wireguard Server

The server has all clients in our network listed as peers with their respective IP address. For the two clients which expose their respective local networks to the other VPN clients, the subnet of the local network should also be added.

# Wireguard Server Configuration

[Interface]
PrivateKey = <SERVER-PRIVATE-KEY>
Address = 10.0.0.1/24
ListenPort = 51820
PreUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE


[Peer]
# Local Server 1
PublicKey = <LOCAL-SERVER-1-PUBLIC-KEY>
AllowedIPs = 10.0.0.2/32, 192.168.8.0/24

[Peer]
# Local Server 2
PublicKey = <LOCAL-SERVER-1-PUBLIC-KEY>
AllowedIPs = 10.0.0.3/32, 192.168.9.0/24

[Peer]
# Laptop
PublicKey = <LOCAL-SERVER-1-PUBLIC-KEY>
AllowedIPs = 10.0.0.4/32

[Peer]
# Smartphone
PublicKey = <SMARTPHONE-PUBLIC-KEY>
AllowedIPs = 10.0.0.5/32

Local Servers

For the local server on Network 1, any traffic to another device on the VPN (10.0.0.0/24) and traffic to local network 2 (192.168.9.0/24) is redirected through the Wireguard VPN server. The server is also able to reroute Wireguard traffic to a device in the local network. Since this server runs the DNS server, it does not need to be specified here.

# Local Server 1 Wireguard Configuration

[Interface]
Address = 10.0.0.2/24
PrivateKey = <LOCAL-SERVER-1-PRIVATE-KEY>
PreUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
Endpoint = <SERVER-PUBLIC-IP>:51820
AllowedIPs = 10.0.0.0/24, 192.168.9.0/24

The configuration for local server 2 is similar, except that it uses the DNS resolution from server 1. It is also able to redirect local traffic to local network 1 (192.168.8.0/24).

# Local Server 2 Wireguard Configuration

[Interface]
Address = 10.0.0.3/24
PrivateKey = <LOCAL-SERVER-2-PRIVATE-KEY>
DNS = 10.0.0.2
PreUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
Endpoint = <SERVER-PUBLIC-IP>:51820
AllowedIPs = 10.0.0.0/24, 192.168.8.0/24

Mobile Clients

Both mobile clients redirect their traffic to the Wireguard server if it is destined to another device on the Wireguard VPN, local network 1 or local network 2. All other traffic does not go through the Wireguard server. To redirect all traffic just change AllowedIPs = 0.0.0.0/0. They also use the DNS resolution from the DNS server in network 1.

# Laptop Wireguard Configuration

[Interface]
Address = 10.0.0.4/24
PrivateKey = <LAPTOP-PRIVATE-KEY>
DNS = 10.0.0.2

[Peer]
Endpoint = <SERVER-PUBLIC-IP>:51820
AllowedIPs = 10.0.0.0/24, 192.168.8.0/24, 192.168.9.0/24
# Smartphone Wireguard Configuration

[Interface]
Address = 10.0.0.5/24
PrivateKey = <SMARTPHONE-PRIVATE-KEY>
DNS = 10.0.0.2

[Peer]
Endpoint = <SERVER-PUBLIC-IP>:51820
AllowedIPs = 10.0.0.0/24, 192.168.8.0/24, 192.168.9.0/24