guide:

Self-Host Safely with WireGuard

Preamble

Click to show

If you're like me, you don't like relying on other people for your online services, either because you don't want to give out your data unnecessarily, or just because you don't want to shell out for subscription services. You might be willing to host such services yourself, and you might even have access to spare hardware to do it on.

One critical issue arises, however: your home network. Maybe you're behind some weird impenetrable NAT setup you don't control, or maybe your IP address is dynamic and changes regularly, or maybe you just don't want to expose your home IP to the world. What then?

Luckily for us, there's a way to circumvent all that nonsense. A custom VPN like the one we'll be setting up today, hosted on an external server like a VPS, can punch through any weird home internet configuration while also swapping out your home IP address for a static one that you can feel safe exposing to the world.

That said, this isn't the be-all end-all for security. I don't claim to know everything about that, and there's probably something you're missing in that regard if you stop here. This is intended as a basic guide for exposing a machine in your home to the internet without messing with your home network or exposing your home IP address.

This guide borrows heavily from the landchad.net WireGuard guide, but while their one is geared toward running a VPN for your personal machine (complete with a tutorial on setting up a WebSocket tunnel), this one will focus specifically on forwarding incoming traffic from one server to another over the internet.

There are some reasons you'd want to do this instead of just hosting everything on the same VPS - in my case, I wanted to host a Minecraft server, and I had a spare computer at home that was better up to the task than anything for rent in my price range. WireGuard can run on pretty much anything; the cheapest tier on Vultr, DigialOcean or Linode will be more than sufficient.

Now on to the good stuff

This guide will take you through the process of setting up a WireGuard VPN connection between two machines running Linux, and configuring it to forward incoming web traffic from one to the other. It assumes you already have two servers set up running Linux (Ubuntu Server in my case, but any distro should suffice) with UFW configured and enabled, and know what ports you'll need.

The machine running your WireGuard server should be an external one exposed to the internet, such as a VPS, for maximum effect. For our purposes, this machine will be the "server", and the one you're forwarding traffic to will be the "client".

We'll be configuring WireGuard with a 172.16.0.1/24 virtual network and forwarding port 25565, the default for Minecraft, but any private IP range and port will do.

Installation

First, we'll install WireGuard on both machines:

apt install wireguard wireguard-tools

And allow the port it uses in UFW:

ufw allow 51820

On the server

Uncomment this line in /etc/sysctl.d/99-sysctl.conf to enable IPv4 forwarding:

net.ipv4.ip_forward=1

And apply the change with this command:

sysctl -w net.ipv4.ip_forward=1

On the client

We'll need to generate a public/private key pair for each machine on our VPN network. Do that now for our client like so:

sudo bash -c "umask 077 ; wg genkey > /etc/wireguard/client_priv.key"
sudo bash -c "wg pubkey < /etc/wireguard/client_priv.key > /etc/wireguard/client_pub.key"

This generates a private key for WireGuard and then generates a corresponding public key based on it. Our client's keys can now be found in /etc/wireguard/client_priv.key and /etc/wireguard/client_pub.key for our private and public keys respectively.

Back to the server

Now do the same for the server:

umask 077 ; wg genkey > /etc/wireguard/server_priv.key
wg pubkey < /etc/wireguard/server_priv.key > /etc/wireguard/server_pub.key

Our server's keys can now be found in /etc/wireguard/server_priv.key and /etc/wireguard/server_pub.key for our private and public keys respectively.

Now, create a WireGuard configuration file at /etc/wireguard/wg0.conf. wg0 will be the name of our network interface, you can name yours something else if you'd like.

[Interface]
Address = 172.16.0.1/24
ListenPort = 51820
PrivateKey =   #(server's private key goes here)
# Firewall rules
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
# Client #1 details
PublicKey =   #(client's public key goes here)
# Traffic to route to this client
AllowedIPs = 172.16.0.2/32

Paste the server's private key after PrivateKey = in [Interface] and the client's public key after PublicKey = in [Peer].

With this setup, our server will use the virtual local IP 172.16.0.1 and our client will use the IP 172.16.0.2. You can also add up to 254 more clients by duplicating the [Peer] block and and updating it with the respective public key and a new local IP address.

Now, enable and start the WireGuard service with

systemctl enable --now wg-quick@wg0.service

wg0 being what we called our network interface before.

Back to the client

Create a WireGuard configuration file for our client at /etc/wireguard/myvpn.conf (you can replace myvpn with a different name if you so choose):

[Interface]
Address = 172.16.0.2/24
PrivateKey =   #(client's private key goes here)
# Optionally, set to your desired DNS server
# DNS = 9.9.9.9

[Peer]
PublicKey =   #(server's public key goes here)
# Endpoint (server) can be a domain name or IP address
Endpoint = (server's public IP goes here):51820
# Traffic to route to server
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Make sure you use your server's public IP here. The PersistentKeepalive option is there because WireGuard closes the connection by default if no data comes through for a while and will only reopen it for the client, so here we send a KeepAlive packet every 25 seconds to prevent that in order to keep our client accessible from the internet.

Now we start WireGuard:

sudo wg-quick up myvpn

myvpn being whatever you called your vpn before. Shut WireGuard down by typing down instead of up here. At this point, you should be able to ping the server (172.16.0.1) or any URL from the client and get a response.

Port Forwarding

Now we'll configure the firewall and port forwarding. This bit assumes you already have UFW configured and enabled. You can set it up now if necessary.

On the server

Allow forwarding for the ports you need:

ufw route allow proto tcp to 172.16.0.2 port 25565

25565 being your relevant port. Repeat this command as necessary.

Now we need to configure iptables. We can do this through UFW by adding the following onto the very end of the file /etc/ufw⁠/before.rules:

*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -i eth0 -p tcp --dport 25565 -j DNAT --to-destination 172.16.0.2
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT

Where 25565 is, again, your relevant port. Copy the -A PREROUTING line for every port you need.

Now restart UFW:

ufw reload

On the client

Allow the relevant ports and restart UFW:

ufw allow 51820
ufw allow 25565
ufw reload

And you're done! At this point, your client should be accessible from the IP address of your server on the ports you forwarded.