Go Back

Port Forwarding with Wireguard and UFW

Let's say you got a cheap VPS just hosting minimal stuff, like a Git or a HTTP server, and you also got a rather powerful home server that is capable of hosting demanding software, like a modded minecraft server. If you wanted to open the stuff you were hosting on your home server to the internet, chances are you probably wouldn't be able to do it without paying your ISP money for static IP. Nowadays, ISPs are utilizing CGNAT to save on public IPs, and that prevents us from just porwarding our ports freely. As we already have a VPS with its own dedicated IP in this case, what we can do is set up a VPN and forward requests going into our VPS on a dedicated port to our home server using NAT.

In this schema, the VPS will be our Wireguard "host", and the home server will be our "client". For the case of Minecraft, we would want to forward incoming connections over port 25565 on our host's public facing interface, which in my case "eth0", to the client, over the VPN interface "wg0" using DNAT, and route the response back again using SNAT.

First and foremost, we need to enable IPv4 forwarding on our VPS so that we do not scratch our heads later on thinking why its not working.

sudo sysctl -w net.ipv4.ip_forward=1

To make this setting persistent, we need to add "net.ipv4.ip_forward=1" without quotes to /etc/sysctl.conf

For configuring, I will be using "wg-quick" as it makes things easier for us. Our host configuration will look something like this:

Host: /etc/wireguard/wg0.conf

[Interface] Address = 10.0.0.1/32 ListenPort = {Wireguard Port} PrivateKey = {Super Secret Key} PostUp = ufw route allow in on wg0 out on eth0 PostUp = ufw route allow proto tcp from any port 25565 to 10.0.0.2 port 25565 PostUp = iptables -t nat -A PREROUTING -p tcp --dport 25565 -j DNAT --to-destination 10.0.0.2:25565 PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PreDown = ufw route delete allow in on wg0 out on eth0 PreDown = ufw route delete allow proto tcp from any port 25565 to 10.0.0.2 port 25565 PreDown = iptables -t nat -D PREROUTING -p tcp --dport 25565 -j DNAT --to-destination 10.0.0.2:25565 PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] PublicKey = {Not So Secret Key} AllowedIPs = 10.0.0.2/32

This might look like a handful, but let's break it down to see how simple it actually is.

Address = 10.0.0.1/32 ListenPort = {Wireguard Port} PrivateKey = {Super Secret Key}

These are just standard things, we set the host's internal IP address to 10.0.0.1, and set the private key and listening port.

PostUp = ufw route allow in on wg0 out on eth0

This adds a rule on our firewall to allow traffic coming from our VPN interface wg0 to our public facing interface eth0, its really easy to understand.

PostUp = ufw route allow proto tcp from any port 25565 to 10.0.0.2 port 25565

This adds a rule on our firewall again to allow forwarding incoming connections over port 25565 to our client's internal ip 10.0.0.2. Pretty verbose.

PostUp = iptables -t nat -A PREROUTING -p tcp --dport 25565 -j DNAT --to-destination 10.0.0.2:25565

This is kind of a handful, lets break it down further.

iptables: the tool we use to configure the forwarding
-t nat: specify that we want to work on NAT table
-A PREROUTING: append a rule to the PREROUTING chain of the table
-p tcp: Specifying tcp protocol to use
--dport 25565: Specifying the destination port
-j DNAT: Specifying the target of the rule, which is DNAT (Destination NAT)
--to-destination: The new destination address that will be rewritten

So what we are basically doing here is rewriting package addresses over port 25565 on the PREROUTING chain.

PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

When I previously said that we will be using SNAT, I kind of lied. Ideally we would want to rewrite all the responses coming from our client, so what we are doing here is we are masquerading packages coming from our client to our public facing interface eth0 as if they were originating from our host. Let's also break this down.

iptables: the tool we use to configure the forwarding
-t nat: specify that we want to work on NAT table
-A POSTROUTING: append a rule to the POSTROUTING chain of the table
-o eth0: Specify the output interface
-j MASQUERADE: Specify the target of the rule.

PreDown = ufw route delete allow in on wg0 out on eth0 PreDown = ufw route delete allow proto tcp from any port 25565 to 10.0.0.2 port 25565 PreDown = iptables -t nat -D PREROUTING -p tcp --dport 25565 -j DNAT --to-destination 10.0.0.2:25565 PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

This is pretty basic, we are just cleaning after ourselves when the wireguard interface is down.

[Peer] PublicKey = {Not So Secret Key} AllowedIPs = 10.0.0.2/32

This is just the client portion, we just specify its public key and give it the address 10.0.0.2.

So with the configuration out of the way, we can just simply turn wireguard on using wg-quick, and then move on to our client configuration, which is much simpler yet.

wg-quick up wg0 OR systemctl enable --now wg-quick@wg0

Client: /etc/wireguard/wg0.conf

[Interface] Address = 10.0.0.2/32 PrivateKey = {Do Not Share Anywhere} Table = 74 PostUp = ip rule add pref 1453 from 10.0.0.2 lookup 74 PostDown = ip rule del pref 1453 [Peer] PublicKey = {Public Key of the Host} AllowedIPs = 0.0.0.0/0 Endpoint = x.x.x.x:{Wireguard Port} PersistentKeepalive = 25

As you can see, its much more simpler, but we are still doing something related to routing. Let's break it down.

[Interface] Address = 10.0.0.2/32 PrivateKey = {Do Not Share Anywhere}

Again, pretty standard stuff. We just set our IP address to 10.0.0.2 and set our private key. We don't really have to specify port.

Table = 74 PostUp = ip rule add pref 1453 from 10.0.0.2 lookup 74

So this part is interesting (not really). All we are really saying is we set a table with some arbitrary value, and say that we want traffic originating from IP address 10.0.0.2 to use routing table 74 with a preference of an arbitrary value 1453. Which translates to saying that we want to route our responses back to the wg0 interface which our requests are originating from. Let's break down the code.

ip: tool to configure routing
rule add: we want to add a rule
pref 1453: arbitrary preference value for prioritization
from 10.0.0.2: the source IP address which we rewritten in the host
lookup 74: specify the routing table to use for packets

PostDown = ip rule del pref 1453

Just cleaning after ourselves again, when the interface goes down.

[Peer] PublicKey = {Public Key of the Host} AllowedIPs = 0.0.0.0/0 Endpoint = x.x.x.x:{Wireguard Port} PersistentKeepalive = 25

The host portion of our configuration. We set its public key, and say that we allow all the IP addresses coming from host. We also specify the endpoint and listening port of the host. Then we set a keepalive value of 25 to not drop connections.

So, why did we go for the trouble of setting up additional routing on our client configuration? Because in my case, I do not want all my traffic to be routed to wg0. I just want the connections coming from wg0 to be routed to wg0. When you set AllowedIPs to 0.0.0.0/0, wg-quick automatically sets routing table so that all the connection is routed through wg0. If we then turn it off by saying Table = off, then we can listen to incoming connections sure, but we have no way to respond back since the packets do not know where to go without proper routing. You might also notice that I do not have any UFW configurations on my client, and that is because my home server is not open to public normally, so there really is not much reason to use it.