Use HAProxy and WireGuard to proxy traffic from your VPS to your home network

Use HAProxy and WireGuard to proxy traffic from your VPS to your home network

Having a server at home (will call this “NAS”) is neat. You can play around with hardware and software. You do not have to worry about privacy and have to worry a lot about backups, redundancy and data loss. You can run your own cloud (or rather Nextcloud) on your own server. However, opening your firewall on your router as well as announcing your private IP address on the internet is not optimal. Having a slim VPS as well, wouldn’t it be great to point all traffic to your VPS and then tunnel some of the traffic to your home network? This way you’d only have to open the port for the tunnel and there’s no need anymore to announce your private IP via a DNS.

As I have used WireGuard (Donenfeld) as VPN before due to his easy setup and fast speed, this idea seemed like a piece of cake. In order to easily send the traffic either to the service on the VPS or to the NAS (or your server at home), HAProxy (HAProxy Website) seemed like a good choice as it is also easy to setup and we only require LB features essentially.

Prerequisites

For this two work you will need a static IPv4 address for your home router or use an DynDNS service. People using FritzBox routers can use the AVM service “MyFritz”. We require this later so that we can maintain a tunnel from our VPS to our server at home.

Current setup and plan

This is the current setup, basically exposing port 80 and 443 from my NAS to the internet. This also requires me to use a DynDNS service as I don’t have a static IP address, effectively always exposing my private IP addresses.

Old setup architecture
Architecture of the old setup

This is the planned architecture. With this now exposing of my private IP is needed anymore and no ports other than the WireGuard port have to be opened.

Architecture of the new setup
Architecture of the new setup

Configure WireGuard

It’s time to set up WireGuard on the VPS and the server at home. If you have a VPN gateway it would also be possible to setup the tunnel from your VPS to your gateway and then just forward the request. However, for me I could not make it work with getting and logging the original IP correctly with this setup, so I opted for running WireGuard directly on the server at home and create a direct point-to-point tunnel. Furthermore, this section is really more of a quick setup. If you would like a more in detail introduction refer to (Donenfeld).

NAS

First install WireGuard via your favourite installing method, e.g. via sudo apt install wireguard. Next, execute the following commands as the root user:

cd /etc/wireguard
umask 077

Create a private key via the wg util and derive a public key from it.

wg genkey | tee privatekey | wg pubkey > publickey

Create a WireGuard config file. You could call this file any name, e.g. vps.conf. We are going with wg0 here.

touch wg0.conf

After installing WireGuard, setting up keys and adding a default config it’s time to dive into the actual WireGuard config. Add the following to the previously created config file in /etc/wireguard/wg0.conf. For <private_key> add the contents of the previously created file privatekey. For the IPv4 addresses you can choose whatever you like, but it’s recommended to use a IP from the private IP spaces as they are not allocated to any specific organisation (Private Network). We could also add a private IPv6 address here, but as we are only dealing with an internal network over WireGuard and our VPS is reachable by IPv6, there’s really no need. Last but not least, the public key of the VPS will be generated in the section next section (“VPS”).

At this point it’s also a good idea to leave root and fall-back to your default user.

[Interface]
# This is the servers (in this case our NAS') privatekey
PrivateKey = <private_key>

# The port you want WireGuard to listen on
ListenPort = 31234

# The IPv4 and IPv6 address if your WireGuard interface
Address = 10.10.20.1/24

[Peer]
PublicKey = <public_key_of_vps>

# The allowed IPs the VPS can take
AllowedIPs = 10.10.20.2/32

Now we only need to bring the WireGuard interface up via

sudo wg-quick up wg0

where wg0 is the name of the file you chose earlier. To make this a bit more resilient in case of restarts, I would recommend enabling this as systemd service with

sudo systemctl enable wg-quick@wg0.service

Now we don’t have to deal with wg-quick up wg0 and wg-quick down wg0 any longer, but can use

sudo systemctl restart wg-quick@wg0

VPS

After having set up our WireGuard server on our NAS, it’s time to do the same on our VPS. The procedure is similar. Install WireGuard, then change to the root user, generate the keys and last create the empty config file.

cd /etc/wireguard
umask 077
wg genkey | tee privatekey | wg pubkey > publickey
touch wg0.conf

Next, add the following contents to your newly created config file.

[Interface]
Address = 10.10.20.2/32
PrivateKey = <private_key_of_vps>

[Peer]
PublicKey = <public_key_of_nas>
AllowedIPs = 10.10.20.1/32
Endpoint = <hostname_or_ip_address_of_your_home_network>:31234

And let’s also enable WireGuard via systemd.

sudo systemctl enable wg-quick@wg0.service

Test the setup

For testing the setup we can use ncat. If you have any firewalls on your VPS or NAS, you might have to open the port 5500 temporarily. On the NAS run

ncat -l 5500

and then on your VPS run

echo "The tunnel works!" | ncat 10.10.20.1 5500

And hopefully on the NAS you will see

user@nas:~$ ncat -l 5500
The tunnel works!

Configure HAProxy on the VPS

The next step is to configure HAProxy on the VPS to act as kind of a load balancer, just not balancing any loads but rather directing traffic based on the requested site.

Referring to our architecture diagram in the introduction we have a site running on the VPS with the FQDN service.fqdn.de and a reverse proxy running on port 8443. First, we need to install HAProxy. Next, put the following into /etc/haproxy/haproxy.cfg.

listen https-server
    # Bind to port 443 for IPv4 and IPv6
    bind :::443 v4v6
    # Enable TCP mode. 
    # We don't want to do operate on the HTTP (application) here. 
    # The reverse proxies will do that
    mode tcp
    # Wait for a maximum of 3 seconds for every request to decide what to do
    tcp-request inspect-delay 3s

    # Accept only if a correct SSL hello was send
    tcp-request content accept if { req.ssl_hello_type 1 }
    # Reject the request if we waited long enough
    tcp-request content reject if WAIT_END

    # Require the client to send SNI, 
    # then check if the requested hostname matches 
    acl is_vps_site req_ssl_sni service.fqdn.de

    # Use our VPS reverse proxy if this site is requested
    # weight is set to 0 so we always consider this server
    use-server vps-backend if is_vps_site weight 0
    server vps-backend 127.0.0.1:8443

    # Send all other traffic to our NAS and 
    # use the proxy v2 protocol to maintain the original IP address
    server default 10.10.20.1:443 check send-proxy-v2


# Listen on port 80 and just redirect all traffic to HTTPs per default
listen http-server
    bind :::80 v4v6
    mode http
    option forwardfor 
    http-request redirect scheme https

And that’s all there is for our VPS.

Configuration changes for nginx (on the VPS or the NAS)

Suppose we already have a working nginx working as reverse proxy, we need to do a few tweaks. For me, this is mostly necessary due to me wanting to stay within my LAN when accessing services on my NAS by using a local DNS which I configured to point to the NAS’ private IP inside my home network. However, as we are sending traffic via the proxy protocol, we also need to tell nginx to receive traffic via that.

The following is a very minimal working excerpt. The important part is the listening on the WireGuards interface IP.

server {
    # Listen on the WireGuard interface's IP on port 80 
    # with activated proxy protocol
    listen 10.10.20.1:80 http2 proxy_protocol;
    
    # Listen on all interfaces
    listen 80;
    listen [::]:80;
    server_name nas1.fqdn.de;

    return 301 https://nas1.fqdn.de$request_uri;
}

server {
    # Listen on the WireGuard interface's IP on port 443 
    # with activated proxy protocol
    listen 10.10.20.1:443 ssl http2 proxy_protocol;

    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    location / {
        # Connect to local port
        proxy_pass https://127.0.0.1:8083;
    }
}

At this stage things are already working as they should. However, if you also want to log the remote user’s original IP address transferred via the proxy protocol, you could add the following line to your nginx.conf or your website’s configuration file.

log_format '$proxy_protocol_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent"';

And with that we are done!

Firewalls

So far we haven’t mentioned firewalls any firewall configs. In your home router you need to open the port you assigned to WireGuard above, e.g. 31234. In case you are using UFW on your NAS, you need to open the same port for UDP via

ufw allow 36594/udp

Bonus: IPv6

In case you are using IPv6 for your communication VPS <-> NAS, make sure to send the endpoint for WireGuard on your VPS to the IPv6 address of your NAS, not of your router.

References

  1. Donenfeld, Jason A. WireGuard: Fast, Modern, Secure VPN Tunnel. https://www.wireguard.com. Accessed 06.03.2021.
  2. HAProxy Website. https://www.haproxy.com/de/. Accessed 06.03.2021.
  3. Private Network. https://en.wikipedia.org/wiki/Private_network. Accessed 06.03.2021.