Skip to main content

dracoli.ch

Mo' script, mo' problems

Self-Hosting Foundry VTT on a Raspberry Pi

How to set up Foundry VTT behind a webserver on a Raspberry Pi, with nginx and SSL.

Note: Players, you don’t need to follow this guide to play the game itself.

But Why Tho

  • you want your game world to be available when your desktop is off
  • you don’t like the idea of paying a recurring hosting fee for your game
  • you want to take on a weekend project to play sysadmin and host a website
  • you’re kind of a control freak too, honestly
  • you have a Raspberry Pi lying around

Prerequisites

Learn the Samba

Creating a Samba file share will make it easier to copy game assets over from your desktop.

SSH into your pi and make the shared folder with

sudo mkdir -m 1777 ~/share

Install Samba with

sudo apt-get update
sudo apt install samba samba-common-bin

Next you’ll want to configure Samba to share your folder

sudo nano /etc/samba/smb.conf # or your favorite text editor

Append this to the end of the file

[share]
Comment = pi shared folder
Path = /home/pi/share
Browseable = yes
Writeable = yes
only guest = no
create mask = 0777
directory mask = 0777
Public = yes

Add a password to your share

sudo smbpasswd -a pi

And reload the samba service using

sudo service smbd restart

Mounting the Share

Assign a static IP to your raspberry pi in your router’s DHCP settings (Instructions will vary by router model).

Now you should be able to access the share from your desktop, by connecting to your pi’s static IP. In Windows, you can use Explorer > This PC > Computer > Add a Network Location with the credentials you set for the share above.

Installing Foundry

Your foundry data needs a home, so let’s make one.

mkdir ~/foundryvtt # this is for foundry itself
mkdir ~/share/foundrydata # this is for your world and modules

For foundry installation instructions, refer to the Node.js for Linux instructions in the official docs.

The rest of this tutorial assumes you use this directory structure and that your local username is pi.

Foundry as a Service

When you installed samba, it also installed a systemd service to start samba whenever your pi started up. You’ll want one of those for foundry so it does the same.

sudo nano /etc/systemd/system/foundryvtt.service
[Unit]
Description=Foundry VTT
After=network.target

[Service]
ExecStart=/usr/bin/node /home/pi/foundryvtt/resources/app/main.js --dataPath=/home/pi/share/foundrydata
WorkingDirectory=/home/pi/foundryvtt
StandardOutput=inherit
StandardError=inherit
Restart=always
User=pi

[Install]
WantedBy=multi-user.target

Note: This service file assumes you used my directory structure from earlier. You’ll need to change those paths if you’ve put foundry and its data somewhere else.

Then register and launch your new service

sudo systemctl daemon-reload
sudo service foundryvtt start
sudo systemctl enable foundryvtt # make the service launch on each startup

Advanced Setup

At this point, we can change foundry’s settings so it serves over port 80 instead of 30000, and we’d be done if all you wanted was the ability for players to connect to your game through the public internet.

But there would still be a couple of rough edges left:

  • your players will have to access your server directly through your IP, which is ugly
  • no TLS? what is this, amateur hour?
  • this is just a foundry instance, not a real webserver

The following sections tackle these problems in order.

Domain Setup

Buying a Domain

This is the fun part, but it’ll cost around $5-15 to get your own little address on the internet. Be as clever as you want. At the time of publishing, murderhobos.party and gargantuan.monster were still available.

Note: The registrar you buy it from doesn’t matter, but not every registrar sells every TLD. I used Uniregistrar/GoDaddy this time, but have used Namecheap before.

Configuring DNS Nameservers

The Domain Name Service translates human-readable domain names to IP addresses, and you’ll need to pick a DNS nameserver to do that for your domain. I’m using Cloudflare, which is free, but you can pick anyone that offers a Dynamic DNS API (below).

Just like how setting up port forwarding depends on your router model, the exact steps for this depends on the domain registrar and nameserver you chose, but the general process is the same. You’ll need to:

  1. Set your DNS nameserver to the one you chose in your registrar’s website
  2. Create an A record in your nameserver to point your domain to your IP.

Dynamic DNS

Most ISPs lease you your IP for a limited amount of time, which means you’ll have to keep updating that URL with your DNS Nameserver. That is annoying. Luckily, you can programmatically announce and update your IP address using Dynamic DNS.

Your router may have a Dynamic DNS client that can periodically update your DNS nameserver with your IP address. Otherwise, you can also set up a Dynamic DNS client, such as ddclient. The distribution of ddclient (or at least the one for rasbian) comes with install hooks that guide you through setup

sudo apt install ddclient
# go through setup
sudo service ddclient status # check if the DDNS update completed successfully
# troubleshoot and edit the ddclient conf file as necessary
# sudo nano /etc/ddclient.conf

TLS/HTTPS

At the beginning, we noted the need to configure your router to forward ports 443 and 80. These are for HTTPS and HTTP, respectively.

Using HTTPS/TLS instead of unsecured HTTP makes it harder to snoop on traffic between users and your server. It’s probably not a big deal if someone gets a copy of your players' rolls, but HTTPS is not a lot of work to set up correctly these days.

Note: Some providers like Cloudflare provide automatic TLS, but it usually requires also using them as a proxy, which can increase latency.

The Let’s Encrypt docs make it easy to obtain a free SSL certificate. At the time of publishing, RPi OS uses Debian Buster, and we’ll be using Nginx below, so you’ll want to follow the instructions in this link for a wildcard certificate.

Note: Don’t set the sslCert or sslKey fields in foundry’s options.json. We’ll cover that in the next section.

Then generate the dhparam OpenSSL will need with

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

nginx

A reverse proxy server like nginx will let users access foundry, your website, and anything else you want to give them access to over the same HTTPS port.

sudo apt-get install nginx
sudo nano /etc/nginx/sites-enabled/mysite.com.conf # replace with your domain name!

Paste this config, swapping out your domain name as necessary

# http -> https
server {
    listen 80;
    server_name  *.mysite.com mysite.com;
    return 301 https://$host$request_uri;
}

# foundry
server {
    listen 443 ssl http2; # omit http2 if using foundry <0.8.x
    server_name foundry.mysite.com;
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;

    # intermediate configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;

    # Sets the max upload size to 50 MB
    client_max_body_size 50M;

    default_type  application/octet-stream;
    location / {
        # pass everything from foundry subdomain to foundry
        proxy_pass http://127.0.0.1:30000;

        # gotta do some stuff for the websockets foundry uses
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        # set proxy headers
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# webserver
server {
    listen 443 ssl;
    server_name *.mysite.com www.mysite.com mysite.com;
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;

    # intermediate configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;

    default_type application/octet-stream;
    
    root /var/www/mysite.com;

    index index.html index.htm index.nginx-debian.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # certbot acme
    location ~ /.well-known {
        allow all;
    }
}

Each server {} block tells nginx a rule to run your webserver:

  1. upgrade any HTTP requests (port 80) to HTTPS (port 443)
  2. pass any traffic you get for https://foundry.mysite.com to the foundry server running on port 30000, using the SSL certs we made
  3. try to match any other traffic to static files on the pi at /var/www/mysite.com and return 404 if it’s not found, again using the SSL certs we made

Note: This config uses a subdomain foundry.mysite.com. You’ll have to add any subdomains you use to your A records/DDNS setup

Also tell foundry you are using a reverse proxy in front of it, so your server can be trusted to host audio/video calls.

nano ~/share/foundrydata/Config/options.json

# then inside nano, add the following inside the curly braces

"hostname": "foundry.mysite.com",
"proxySSL": true,
"proxyPort": 443

Start the proxy with

sudo service nginx start
sudo service foundryvtt restart

Now you should be able to access your website at https://mysite.com and your foundry instance at https://foundry.mysite.com

Note: At this point, if you’re not able to access the site, you should check the following in order: foundry service up and running (use your local IP directly), port forwarding on your router (access your public IP directly), dns records point to your public IP, nginx is setup to forward foundry traffic to your subdomain, nginx is running.

Note: If nginx fails to start, try checking the logs with sudo service nginx status. Particularly for sites with longer domain names, you might see a could not build the server_names_hash error. The error message will point you to something that you’ll need to add into the http block in /etc/nginx/nginx.conf.

Whew. Enjoy your fancy new foundry server.