tedtriv.co.uk

Putting a Home Server Behind a VPS

Tags:

The Raspberry Pi at home

Recently I have set up Forgejo on a Raspberry Pi. To help reduce the network load on my own server, I have put all traffic through a VPS with nginx and WireGuard.

While I was setting this up I noticed so many of the guides online were AI slop and were incomprehensible or didn’t meet my use case. I wanted to be able to direct traffic to Forgejo into my VPS which was connected to the Pi.

The amount of LLM-generated content on the internet has made finding solutions to technical problems virtually impossible. None of these sites were any good in the first place, but now they are totally impenetrable and make “Search Engine Optimisation optimised”[sic] sites look good. I never considered “AI”/LLM-free spaces bubbles, but I much prefer them!

WireGuard Setup

On Both Machines

  1. Install WireGuard
  2. Create the public and private keys (see Key generation)
  3. In /etc/wireguard create a file called wg0.conf (or anything ending in .conf)

Note: The keys should only be readable as the root user. Run umask 077 before generating the keys - WireGuard will tell you to do this if you haven’t already.

It is also possible to encrypt your keys if you prefer.

On Your Home Server

Firstly, you will have to do the port forwarding for your device on your port. This varies depending on your router/ISP.

Enter this into wg0.conf:

[Interface]
Address = 172.16.0.1/24
PrivateKey = HOME_PRIVATE_KEY
ListenPort = PORT

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]
PublicKey = VPS_PUBLIC_KEY
AllowedIPs = 172.16.0.2/32

The IP addresses are local ones that will be used to talk between the machines. The number after the slash is to do with the subnet masks. If you don’t know what it does, you don’t need to touch it.

The PostUp/Down commands will set up IP forwarding which seems to be required on some servers. There are quite a few other options that appear in guides online, so see what works for you.

On Your VPS

Enter this into wg0.conf:

[Interface]
Address = 172.16.0.2/24
PrivateKey = VPS_PRIVATE_KEY
ListenPort = PORT

[Peer]
PublicKey = HOME_PUBLIC_KEY
Endpoint = SERVER_IP:PORT
AllowedIPs = 172.16.0.1/32

This configuration is the same, except we set up the keys to talk to the server and don’t need to set up IP forwarding.

The AllowedIPs has been set to only use the VPN if we want to talk to the VPS. If we were to set up a VPN for general use, we would use 0.0.0.0/0, ::/0 to allow all IPv4 and v6 addresses.

Activating the VPN & Testing It

If you have any firewalls, allow UDP traffic through the port you chose.

Run wg quick up wg0 on your home server and VPS (change wg0 to whatever you called your config)

To test the connection, run this on your home server:

nc -vvlnp PORT

And this on your VPS:

dd if=/dev/zero bs=1024K count=1024 | nc -v 172.16.0.1 PORT

You can check the status by running wg on either side.

SystemD Service

Lastly, enable the systemd service to automatically start it when booting:

systemctl enable wg-quick@wg0.service

Again change wg0 to your config’s name.

nginx Setup

Nginx will be our “entry point” for traffic while we use Forgejo’s own HTTP server.[1] We pass traffic to Forgejo with proxy_pass in the nginx config. Don’t worry about TLS after nginx - WireGuard encrypts the connection to the home server for us.

  1. Install nginx
  2. Create a file with the following in /etc/nginx/sites-available/yoursite (change yoursite to whatever you like)
server {
    listen 80;
    listen [::]:80;

    # Only include these lines once TLS is set up
    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate /path/to/ssl/cert.pem;
    ssl_certificate_key /path/to/ssl/key.pem;

    server_name example.org;
    merge_slashes off;

    # Only include these lines once TLS is set up
    if ($https != "on") {
        return 301 https://example.org$request_uri;
    }

    # Proxy headers provided by Forgejo Docs
    # Change IP here if needed
    location / {
        proxy_pass http://172.16.0.1:3000/;

        proxy_set_header Connection $http_connection;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        client_max_body_size 512M;
    }
}
  1. Change example.org to whatever your real domain is.
  2. Navigate to /etc/nginx/sites-enabled and run ln -s /etc/nginx/sites-available/yoursite to enable the site, again change the name accordingly
  3. Delete the symlink in sites-enabled called “default” if you want to.
  4. Run nginx -t to check the config is correct, then run nginx -s reload to restart it (or systemctl restart nginx)

TLS Certificates (HTTPS) & WWW redirection

I use Let’s Encrypt to get TLS certificates but I don’t like the way they configure nginx files. I recommend you use these instructions as they do not interfere.

The server isn’t configured properly to use nginx for the HTTP challenge, so you’ll have to switch it off for now and use the standalone HTTP server like this:

certbot certonly --standalone -d 'example.org www.example.org'

As shown above, you can add multiple domains (provided they point to your VPS), but you’d probably want www to redirect to your bare domain (or vice-versa) with this in the same config file:

server {
    listen 80;
    listen [::]:80;

    # Only include these lines once TLS is set up
    # Remember to use the www certs unless you have a wildcard
    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate /path/to/ssl/cert.pem;
    ssl_certificate_key /path/to/ssl/key.pem;

    # Note that this is www.example.org
    server_name www.example.org;
    merge_slashes off;

    # Only include these lines once TLS is set up
    if ($https != "on") {
        return 301 https://example.org$request_uri;
    }

    # Note that this is example.org
    location / {
        return 301 https://example.org$request_uri;
    }
}

Further Reading

By the way, if you want a wildcard SSL cert, you can do it without having to change who does your DNS i.e. using the Cloudflare or Google APIs - the DNS challenge works with CNAME records too and I found the documentation didn’t spell this out well.

Footnotes

[1]: This server can be used without nginx if you ever wanted to, you just need to provide it with the TLS certificates [docs].

[2]: You could keep the default configuration that has the “Welcome to nginx!” page, but I’ve never had any problems with Certbot’s own HTTP sever.