I prefer to host my own infrastructure. This website site is on a server in my home as are my e-mail, Nextcloud, and Gitlab servers. I also encourage those interested to do the same. However, many Internet Service Providers (ISP) are reluctant to issue static IP address to residential customers and often block commonly used ports (especially port 25, which is needed if you want to self-host an e-mail server). While Dynamic Domain Name System (DDNS) mitigates the static IP address problem, it’s not perfect. Moreover, it won’t open ports that your ISP is blocking.
Fortunately, there’s another option. Many Virtual Private Server (VPS) providers happily issue static IP addresses for VPS instances. They also tend to be much less apt to block ports (although port 25 is commonly blocked and often requires submitting a support ticket and jumping through hoops to get unblocked). Wouldn’t it be convenient to use a VPS as a front end for your self-hosted network?
One of the things I played around with a lot recently is WireGuard. WireGuard is a new Virtual Private Network (VPN) technology that is both easy to setup and performant. In this guide I will explain how to use WireGuard to bridge your self-hosted network with a VPS (in my case I’m using this setup with one of my servers through a $5 per month DigitalOcean droplet) so that you can access your servers from the VPS’s static IP address.
This guide will not explain how to install WireGuard or the specifics of WireGuard since that information is better provided by WireGuard’s official website. What this guide will explain is how to create a WireGuard configuration file that can be used via the wg-quick command to forward traffic received via the VPS’s static IP address to individual servers on your self-hosted network.
As a quick aside, the reason I opted to use a configuration file and the wg-quick command instead of setting up a WireGuard interface and iptables on the Linux system directly is because one of my goals is portability. It’s a trivial matter to spin up a new VPS, install WireGuard, upload your configuration file, and run wg-quick. This way if you lose access to your VPS, you can get everything up and running again by spinning up a new VPS and updating your DNS records.
Once your VPS is up and running and WireGuard is installed, go to the /etc
directory and, if it doesn’t already exist, create a wireguard
directory. This is the directory where your configuration file will be store.
You will need a private and public key, which can both be generate via the wg
command. First issue the umask 077
command. This will provide read and write access only to the user account creating the files (which is likely root). Now issue the wg genkey | tee privatekey | wg pubkey > publickey
command. The first part of this command generates your private key and pipes it into the tee
command, which outputs the key into a file named privatekey
and pipes it into the wg pubkey
command, which derives a public key from the private key and writes that public key to a file named publickey
.
At this point you should have two files in /etc/wireguard
, privatekey
and publickey
. Now it’s time to create your initial configuration file. Open your text editor of choice and save the file as wg0.conf
(the file name doesn’t have to be wg0, but the extension has to be .conf). You’ll start by adding the following lines:
[Interface]
Address = 172.16.1.1/16
SaveConfig = false
ListenPort = 65000
PrivateKey =
[Interface]
indicates that the lines that follow are related to the creation of a WireGuard interface.
Address
indicates the IP address that will be assigned to the WireGuard interface. Note that you can assign multiple IP addresses to a WireGuard interface so if you also wanted to give it an IPv6 address you could add the line Address = fd00:cafe:dead:babe::1/64
. If you’d rather have a single line containing all of the assigned IP addresses you can use a comma separated list such as Address = 172.16.1.1/16, fd00:cafe:dead:babe::1/64
(personally I find adding a single item per line more readable so I will stick to that convention for this guide). Also note that the subnet of the WireGuard interface’s IP address should differ from the one you use on your home network. In this example I’m using a 172.16.x.x address because 192.168.x.x and 10.x.x.x addresses are the ones I most commonly encounter on home and corporate networks.
SaveConfig = false
will prevent WireGuard from automatically saving additional information to the wg0.conf
file. I prefer this because otherwise WireGuard has a habit of generating a new fe80:: IPv6 address and saving it to wg0.conf
every time the interface is brought up with the wg-quick
command. Moreover, if SaveConfig
is set to true
any comments you add to the wg0.conf
file will be erased when the wg-quick
command is called (I tend to heavily comment my files so this is a big issue to me).
ListenPort
indicates what port you want the WireGuard interface to be accessible on. The default listen port for WireGuard is 51820. However, I actually have my VPS’s WireGuard interface setup to foward traffic received on port 51820 to another WireGuard server on my home network so I’m using a non-default port in this example.
PrivateKey
indicates the interface’s private key. Paste the private key stored in the privatekey
file here (note that you have to enter the private key itself, not the path to the file containing the private key).
Now you need to configure WireGuard on one of your self-hosted servers. First, generate a private and public key pair on your self-hosted server the same way you generated them on your VPS instance. Then create wg0.conf
and enter the following information:
[Interface]
Address = 172.16.1.2/16
SaveConfig = false
PrivateKey =
There are two changes to note. The first change is the IP address. The IP address of the WireGuard interface on your self-hosted server should be different (but in the same subnet) than the IP address of your VPS’s WireGuard interface. The second change should be obvious. You will enter the private key you generated on your self-hosted server.
Now both your VPS and your self-hosted servers should have WireGuard configuration files. The next step is to tell them about each other. This is done using the [Peer]
directive in the configuration file. Let’s open the wg0.conf
file on your VPS and add the [Peer]
information for your self-hosted WireGuard interface. The file should look like this:
[Interface]
Address = 172.16.1.1/16
SaveConfig = false
ListenPort = 65000
PrivateKey =
[Peer]
PublicKey =
AllowedIPs = 172.16.1.2/32
PublicKey
indicates the public key for your self-hosted server. This should be the key stored in the publickey
file on your self-hosted server.
AllowedIPs
serves two purposes. The first purpose is to inform the VPS what IP addresses can be used as source addresses when communicating with the peer. The line AllowedIPs = 172.16.1.2/32
indiates that the address 172.16.1.2
is the only source IP address allowed to encrypt traffic that can be decrypted with provided public key. The second purpose is to act as a routing table. Traffic destined for 172.16.1.2
will be encrypted with the provided public key so it can be decrypted by the WireGuard interface on your self-hosted server.
Depending on your goals, you may wish to use less strict values for AllowedIPs
. However, I prefer to keep them strict when setting up a WireGuard connection to bridge between a VPS and my self-hosted network because I actually configure multiple peers and forward different traffic to different peers.
Now edit the wg0.conf
file on your self-hosted WireGuard server so it looks like this:
[Interface]
Address = 172.16.1.2/16
SaveConfig = false
PrivateKey =
[Peer]
PublicKey =
AllowedIPs = 172.16.1.1/32
Endpoint =
PersistentKeepalive = 25
The PublicKey
directive should obviously have the public key generated on the VPS and AllowedIPs
directives should have the IP address of the VPS’s WireGuard interface. However, there are two new entries.
Endpoint
tells the WireGuard interface the IP address to which it should communicate. This should be set to the static IP address assigned to the VPS. Why wasn’t the Endpoint
directive needed in the VPS’s configuration file? Because WireGuard has a neat feature, which enables roaming. When a WireGuard interface receives a packet, it notes the source address and uses that address when sending replies. This means that should the IP address of your self-hosted network change (which can happen periodically on networks not assigned a static IP address) the VPS WireGuard interface will note the new source address and use it as the new destination address.
PersistentKeepalive
indicates how frequently the WireGuard interface should send keep alive messages to its peer. WireGuard is a pretty quiet protocol by default. It abstains from sending unnecessary traffic. While this makes for a more efficient protocol, it causes issues with peers behind a Network Address Translation (NAT) device. When a peer behind a NAT device connects to an external server, the NAT device keeps track of the connection. If no traffic is observed on the connection for a while, the connection is timed out and the NAT device forgets it. Setting PersistentKeepalive
ensures traffic is periodically sent (once every 25 seconds in the case of the above configuration) across the connection and thus will not be forgotten by a NAT device middle man.
With these configurations in place, we can bring up both WireGuard connections. On the VPS issue the command systemctl start wg-quick@wg0
then issue the same command on your self-hosted server. From your VPS you should be able to ping 172.16.1.2
and from your self-hosted server you should be able to ping 172.16.1.1
. If you can’t ping either peer, there’s likely an error in one of your configuration files or a firewall hindering the communications.
If both peers can ping each other, congratulations, you’ve successfully setup a WireGuard connection and are done with the self-hosted side of your configuration. There remains one last thing to do on your VPS though. At the moment traffic received on the VPS’s static IP address isn’t forwarded through the WireGuard interface and thus will never reach your self-hosted server.
To correct this, you first need to enable packet forwarding on your VPS. On most Linux distributions this is done by adding the lines net.ipv4.ip_forward=1
and, if you want the capability to forward IPv6 packets, net.ipv6.conf.all.forwarding=1
to the sysctl.conf
file then issuing the sysctl --system
command. Once packet forwarding is enabled, you can start adding packet forwarding capabilities to your WireGuard configuration file.
wg-quick
recognizes a few useful directives. Two of them that we will use here are PostUp
and PostDown
. PostUp
executes commands immediately after your WireGuard interface is brought up. PostDown
executes commands immediately after your WireGuard interface has been torn down. These are convenient moments to either add port forwarding information or remove it.
To start with enable port forwarding by adding the following lines under the [Interface]
section of the VPS’s wg0.conf
file (as with the Address
directive, the PostUp
and PostDown
directives can appear in the file multiple times so you can split up a lengthy list of commands onto separate lines):
PostUp = iptables -A FORWARD -i eth0 -o %i -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
PostUp = iptables -A FORWARD -i %i -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
These two lines enable packet forwarding from interface eth0
to wg0
and from wg0
to eth0
for established and related connections. Once a connection has been established, all packets for that connection will be properly forwarded by WireGuard to the appropriate peer. Do note that the interface name of your VPS’s network connection may not be eth0
. To find the name use the ip address list
command. The interface that has your VPS’s assigned static IP address is the one you want to put in place of eth0
. %i
will be replaced by the name of your WireGuard interface when the wg-quick
is run.
If the WireGuard interface isn’t up, there’s no reason to keep forwarding enabled. In my configuration files I prefer to undo all of the changes I made with the PostUp
directives when the interface is torn down with PostDown
directives. To do this add the following lines:
PostDown = iptables -D FORWARD -i eth0 -o %i -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
PostDown = iptables -D FORWARD -i %i -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
These commands look almost identical except they use the -D flag (delete) instead of the -A flag (append).
Now we need to tell the VPS to change the source address of all forwarded packets to the IP address of the WireGuard interface, which can be done by adding the following line:
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE;
If the source address isn’t changed on forwarded packets, the responses to those packets won’t be properly forwarded back through the WireGuard interface (and thus will never reach the client that connected to your server). To undo this when the WireGuard interface goes down, add the following line:
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE;
Now that forwarding has been enabled we can start adding commands to forward packets to their appropriate destinations. For this example I’m going to assume your self-hosted server is running a Hypertext Transfer Protocol (HTTP) server. HTTP servers listen on two ports. Port 80 is used for unsecured traffic and port 443 is used for secured traffic. To forward traffic on ports 80 and 443 from your VPS to your self-hosted server add the following lines:
PostUp = iptables -A FORWARD -i eth0 -o %i -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT;
PostUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 172.16.1.2;
PostUp = iptables -A FORWARD -i eth0 -o %i -p tcp --syn --dport 443 -m conntrack --ctstate NEW -j ACCEPT;
PostUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 172.16.1.2;
The first command will forward all packets received on port 80 of eth0
through the WireGuard interface. The second command changes the designation IP address to the self-hosted WireGuard peer, which will ensure the packet is properly routed to the self-hosted server. Lines three and four do the same for traffic on port 443. To undo these changes when the WireGuard interface goes down, add the following lines:
PostDown = iptables -D FORWARD -i eth0 -o %i -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT;
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 172.16.1.2;
PostDown = iptables -D FORWARD -i eth0 -o %i -p tcp --syn --dport 443 -m conntrack --ctstate NEW -j ACCEPT;
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 172.16.1.2;
At this point the configuration file on your VPS should look like this:
[Interface]
Address = 172.16.1.1/16
SaveConfig = false
ListenPort = 65000
PrivateKey =
PostUp = iptables -A FORWARD -i eth0 -o %i -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
PostUp = iptables -A FORWARD -i %i -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE;
PostUp = iptables -A FORWARD -i eth0 -o %i -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT;
PostUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 172.16.1.2;
PostUp = iptables -A FORWARD -i eth0 -o %i -p tcp --syn --dport 443 -m conntrack --ctstate NEW -j ACCEPT;
PostUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 172.16.1.2;
PostDown = iptables -D FORWARD -i eth0 -o %i -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
PostDown = iptables -D FORWARD -i %i -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT;
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE;
PostDown = iptables -D FORWARD -i eth0 -o %i -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT;
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 172.16.1.2;
PostDown = iptables -D FORWARD -i eth0 -o %i -p tcp --syn --dport 443 -m conntrack --ctstate NEW -j ACCEPT;
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 172.16.1.2;
[Peer]
PublicKey =
AllowedIPs = 172.16.1.2/32
After the changes are made you’ll need to restart your WireGuard interface on your VPS. Do this by issuing the systemctl restart wg-quick@wg0
command. You may also need to restart the WireGuard interface on your self-hosted server (I’ve had mixed luck with this and usually restart both to be sure). Now you should be able to access your self-hosted HTTP server from your VPS’s static IP address.
I prefer not having to manually start my WireGuard interfaces every time I restart a server. You can make your WireGuard interfaces come online automatically when you system starts by issuing the systemctl enable wg-quick@wg0
command on both your VPS and your self-hosted server.