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.
- You can now install Wireguard on your server by following the instructions for your operating system.
- Enable packet forwarding on the server by editing
/etc/sysctl.conf
and uncommenting the linenet.ipv4.ip_forward=1
. - Create a public-private key-pair in
/etc/wireguard
using:umask 077; wg genkey | tee privatekey | wg pubkey > publickey
- 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>
- Make sure the port specified under
ListenPort
is open in the server’s firewall. - Enable the start of Wireguard on boot with
sudo systemctl enable wg-quick@wg0
- Start the server now with
sudo systemctl start wg-quick@wg0
- 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.
- Install Wireguard on the device by following the instructions for your operating system.
- Create a public-private key-pair in
/etc/wireguard
withumask 077; wg genkey | tee privatekey | wg pubkey > publickey
- 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
- Enable the start of Wireguard on boot with
sudo systemctl enable wg-quick@wg0
- Start the server now with
sudo systemctl start wg-quick@wg0
- 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:
- Stop Wireguard with:
sudo systemctl stop wg-quick@wg0
- 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
- Start the Wireguard server again:
sudo systemctl start wg-quick@wg0
- Verify connection from the server to the client with
ping 10.0.0.2
- 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.
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