<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Nat's Blog]]></title><description><![CDATA[Nat's Blog]]></description><link>https://tamez.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 20:08:04 GMT</lastBuildDate><atom:link href="https://tamez.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[VPS Hosting for FoundryVTT: A Beginner's Guide]]></title><description><![CDATA[Prerequisite Checks
Before you begin, ensure you have:
Required:

A valid FoundryVTT license (purchase at https://foundryvtt.com)

Basic command-line familiarity

VPS hosting ($5-15/month)

SSH client installed:

Windows: Built into Windows 10/11, or...]]></description><link>https://tamez.dev/vps-hosting-for-foundryvtt-a-beginners-guide</link><guid isPermaLink="true">https://tamez.dev/vps-hosting-for-foundryvtt-a-beginners-guide</guid><category><![CDATA[DND]]></category><category><![CDATA[foundry-vtt]]></category><category><![CDATA[vps]]></category><category><![CDATA[ovh]]></category><category><![CDATA[DigitalOcean]]></category><dc:creator><![CDATA[Nat Tamez]]></dc:creator><pubDate>Mon, 05 Jan 2026 04:07:42 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-prerequisite-checks">Prerequisite Checks</h2>
<p>Before you begin, ensure you have:</p>
<p><strong>Required:</strong></p>
<ul>
<li><p>A valid FoundryVTT license (purchase at <a target="_blank" href="https://foundryvtt.com">https://foundryvtt.com</a>)</p>
</li>
<li><p>Basic command-line familiarity</p>
</li>
<li><p>VPS hosting ($5-15/month)</p>
</li>
<li><p>SSH client installed:</p>
<ul>
<li><p><strong>Windows:</strong> Built into Windows 10/11, or download PuTTY</p>
</li>
<li><p><strong>Mac/Linux:</strong> Built-in Terminal</p>
</li>
</ul>
</li>
</ul>
<p><strong>Optional but Recommended:</strong></p>
<ul>
<li><p>A domain name ($10-15/year from Namecheap, Cloudflare, etc.)</p>
</li>
<li><p>SFTP client for file uploads (FileZilla, Cyberduck, WinSCP)</p>
</li>
</ul>
<p><strong>Time Estimate:</strong> 30-60 minutes for complete setup</p>
<hr />
<h2 id="heading-step-1-get-a-vps">Step 1: Get a VPS</h2>
<p>Choose a provider like OVHcloud, DigitalOcean, Hetzner, or Linode.</p>
<p><strong>Recommended specs:</strong></p>
<ul>
<li><p>2GB RAM minimum (4GB recommended)</p>
</li>
<li><p>2 CPU cores</p>
</li>
<li><p>20GB+ SSD storage</p>
</li>
<li><p>Ubuntu 24.04 LTS or Newer</p>
</li>
</ul>
<p>You will need SSH access, most hosts have guides for this</p>
<h2 id="heading-step-2-initial-setup">Step 2: Initial Setup</h2>
<p>SSH into your server. On Windows, use PuTTY, Windows Terminal, or WSL. On Mac/Linux, open a Terminal and run:</p>
<pre><code class="lang-bash">ssh username@your_server_ip
</code></pre>
<p>Replace <code>your_server_ip</code> with your VPS IP address and <code>username</code> with your provider's default user:</p>
<ul>
<li><p><strong>OVHcloud:</strong> Usually <code>ubuntu</code> or <code>debian</code> (depending on OS)</p>
</li>
<li><p><strong>DigitalOcean:</strong> <code>root</code> for some images, <code>ubuntu</code> for Ubuntu droplets</p>
</li>
<li><p><strong>Hetzner:</strong> <code>root</code></p>
</li>
<li><p><strong>Linode:</strong> <code>root</code></p>
</li>
</ul>
<p>Check your provider's setup email or documentation for the correct username. Accept the host key when prompted.</p>
<p><strong>Authentication:</strong></p>
<ul>
<li><p><strong>Password:</strong> If your VPS uses password authentication, you'll be prompted to enter the password (usually emailed to you by your hosting provider)</p>
</li>
<li><p><strong>SSH Key:</strong> If you set up SSH keys during VPS creation, the key will be used automatically. No password prompt will appear.</p>
<ul>
<li><p>For PuTTY users: Load your private key (.ppk file) in Connection &gt; SSH &gt; Auth</p>
</li>
<li><p>For OpenSSH users: Specify the key with <code>ssh -i /path/to/private_key username@your_server_ip</code></p>
</li>
</ul>
</li>
</ul>
<p>Once connected, update packages:</p>
<pre><code class="lang-bash">sudo apt update
sudo apt upgrade -y
sudo apt install unzip -y
</code></pre>
<h2 id="heading-step-3-create-a-dedicated-user">Step 3: Create a Dedicated User</h2>
<pre><code class="lang-bash">sudo useradd -r -m -d /opt/foundry -s /bin/bash foundry
sudo passwd foundry  <span class="hljs-comment"># Set a password for SFTP access</span>
</code></pre>
<h2 id="heading-step-4-install-nodejs-via-fnm-as-foundry-user">Step 4: Install Node.js via fnm (as foundry user)</h2>
<pre><code class="lang-bash">sudo -u foundry -i  <span class="hljs-comment"># Switch to foundry user</span>

curl -fsSL https://fnm.vercel.app/install | bash
<span class="hljs-built_in">source</span> ~/.bashrc
fnm install --lts --corepack-enabled
fnm default lts-latest
fnm use lts-latest

node --version  <span class="hljs-comment"># Verify installation</span>
</code></pre>
<h2 id="heading-step-5-install-pm2">Step 5: Install PM2</h2>
<pre><code class="lang-bash">npm install -g pm2
pm2 --version  <span class="hljs-comment"># Verify installation</span>
</code></pre>
<h2 id="heading-step-6-download-and-install-foundryvtt">Step 6: Download and Install FoundryVTT</h2>
<p>Still as the foundry user:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Get your timed URL from https://foundryvtt.com/me/licenses</span>
<span class="hljs-comment"># (Requires a Foundry license)</span>
<span class="hljs-built_in">cd</span> ~
mkdir -p app
curl -L -o foundry.zip <span class="hljs-string">"YOUR_TIMED_URL_HERE"</span>
unzip foundry.zip -d ~/app
rm foundry.zip

<span class="hljs-comment"># Create data directory</span>
mkdir -p data
</code></pre>
<h2 id="heading-step-7-configure-foundry-options">Step 7: Configure Foundry Options</h2>
<p>Create the Foundry configuration file at <code>~/data/Config/options.json</code>:</p>
<p><strong>Option A: With Domain &amp; HTTPS (Strongly Recommended)</strong></p>
<pre><code class="lang-bash">mkdir -p ~/data/Config
cat &gt; ~/data/Config/options.json &lt;&lt;<span class="hljs-string">'EOF'</span>
{
  <span class="hljs-string">"hostname"</span>: <span class="hljs-string">"foundry.yourdomain.com"</span>,
  <span class="hljs-string">"routePrefix"</span>: null,
  <span class="hljs-string">"sslCert"</span>: null,
  <span class="hljs-string">"sslKey"</span>: null,
  <span class="hljs-string">"port"</span>: 30000,
  <span class="hljs-string">"proxyPort"</span>: 443,
  <span class="hljs-string">"proxySSL"</span>: <span class="hljs-literal">true</span>,
   <span class="hljs-string">"upnp"</span>: <span class="hljs-literal">false</span>
}
EOF
</code></pre>
<p><strong>Important:</strong> Replace <a target="_blank" href="http://foundry.yourdomain.com"><code>foundry.yourdomain.com</code></a> with your actual domain name.</p>
<p>This configuration tells Foundry:</p>
<ul>
<li><p>It's accessible at your domain name</p>
</li>
<li><p>To run on port 30000 (internal)</p>
</li>
<li><p>That it's behind a reverse proxy (Caddy) on port 443</p>
</li>
<li><p>That the proxy handles SSL/TLS</p>
</li>
</ul>
<p><strong>Option B: Without Domain/HTTPS (Not Recommended)</strong></p>
<p>If you want to skip DNS and HTTPS setup, you can use:</p>
<pre><code class="lang-bash">mkdir -p ~/data/Config
cat &gt; ~/data/Config/options.json &lt;&lt;<span class="hljs-string">'EOF'</span>
{
  <span class="hljs-string">"port"</span>: 30000,
  <span class="hljs-string">"upnp"</span>: <span class="hljs-literal">false</span>
}
EOF
</code></pre>
<p>Then open port 30000 in your firewall (<code>sudo ufw allow 30000/tcp</code>) and access via <a target="_blank" href="http://your_server_ip:30000"><code>http://your_server_ip:30000</code></a>.</p>
<p><strong>⚠️ Warning:</strong> This is insecure as traffic is unencrypted. Only use for testing or if using a VPN.</p>
<h2 id="heading-step-8-configure-pm2">Step 8: Configure PM2</h2>
<p>Create <code>~/ecosystem.config.js</code>:</p>
<pre><code class="lang-bash">cat &gt; ~/ecosystem.config.js &lt;&lt;<span class="hljs-string">'EOF'</span>
module.exports = {
  apps: [{
    name: <span class="hljs-string">'foundry'</span>,
    script: <span class="hljs-string">'/opt/foundry/app/main.js'</span>,
    cwd: <span class="hljs-string">'/opt/foundry/app'</span>,
    env: {
      NODE_ENV: <span class="hljs-string">'production'</span>,
      FOUNDRY_VTT_DATA_PATH: <span class="hljs-string">'/opt/foundry/data'</span>
    },
    instances: 1,
    autorestart: <span class="hljs-literal">true</span>,
    watch: <span class="hljs-literal">false</span>,
    max_memory_restart: <span class="hljs-string">'1G'</span>
  }]
};
EOF
</code></pre>
<p>Start Foundry:</p>
<pre><code class="lang-bash">pm2 start ecosystem.config.js
pm2 save
pm2 startup  <span class="hljs-comment"># Copy and run the command it outputs as your regular user</span>
</code></pre>
<p>Exit back to your regular user:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">exit</span>
</code></pre>
<h2 id="heading-step-9-install-caddy-optional-but-strongly-recommended">Step 9: Install Caddy (Optional but Strongly Recommended)</h2>
<p><strong>Why use Caddy?</strong></p>
<ul>
<li><p>Automatic HTTPS with free SSL certificates from Let's Encrypt</p>
</li>
<li><p>Secure encrypted connections (required for many web features)</p>
</li>
<li><p>Professional setup with custom domain</p>
</li>
<li><p>Better security for passwords and game data</p>
</li>
</ul>
<p><strong>⚠️ If you skip Caddy:</strong> You'll need to use <a target="_blank" href="http://your_server_ip:30000"><code>http://your_server_ip:30000</code></a> and manually open port 30000. Traffic will be unencrypted.</p>
<pre><code class="lang-bash">sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf <span class="hljs-string">'https://dl.cloudsmith.io/public/caddy/stable/gpg.key'</span> | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf <span class="hljs-string">'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt'</span> | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
</code></pre>
<h2 id="heading-step-10-configure-caddy">Step 10: Configure Caddy</h2>
<p>Replace <a target="_blank" href="http://foundry.yourdomain.com"><code>foundry.yourdomain.com</code></a> with your actual domain and <a target="_blank" href="mailto:your-email@example.com"><code>your-email@example.com</code></a> with your email:</p>
<pre><code class="lang-bash">sudo tee /etc/caddy/Caddyfile &gt; /dev/null &lt;&lt;<span class="hljs-string">'EOF'</span>
{
    email email@example.com
}

foundry.example.com {
    reverse_proxy localhost:30000

    header {
        Strict-Transport-Security <span class="hljs-string">"max-age=31536000; includeSubDomains; preload"</span>
        X-Content-Type-Options <span class="hljs-string">"nosniff"</span>
        X-Frame-Options <span class="hljs-string">"SAMEORIGIN"</span>
        Referrer-Policy <span class="hljs-string">"same-origin"</span>
    }

    request_body {
        max_size 500MB
    }
}
EOF
</code></pre>
<p>Validate and start Caddy:</p>
<pre><code class="lang-bash">sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl <span class="hljs-built_in">enable</span> caddy
sudo systemctl restart caddy
</code></pre>
<h2 id="heading-step-11-configure-sftp-for-file-uploads">Step 11: Configure SFTP for File Uploads</h2>
<p>Edit SSH config:</p>
<pre><code class="lang-bash">sudo tee -a /etc/ssh/sshd_config &gt; /dev/null &lt;&lt;<span class="hljs-string">'EOF'</span>

<span class="hljs-comment"># Foundry SFTP configuration</span>
Match User foundry
    ForceCommand internal-sftp
    PasswordAuthentication yes
    ChrootDirectory /opt/foundry
    PermitTunnel no
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no
EOF
</code></pre>
<p>Fix permissions for chroot:</p>
<pre><code class="lang-bash">sudo chown root:root /opt/foundry
sudo chmod 755 /opt/foundry
sudo chown -R foundry:foundry /opt/foundry/data
</code></pre>
<p>Restart SSH:</p>
<pre><code class="lang-bash">sudo systemctl restart sshd
</code></pre>
<h2 id="heading-step-12-configure-firewall-if-using-ufw">Step 12: Configure Firewall (if using ufw)</h2>
<pre><code class="lang-bash">sudo ufw allow 22/tcp   <span class="hljs-comment"># SSH/SFTP</span>
sudo ufw allow 80/tcp   <span class="hljs-comment"># HTTP</span>
sudo ufw allow 443/tcp  <span class="hljs-comment"># HTTPS</span>
sudo ufw <span class="hljs-built_in">enable</span>
</code></pre>
<h2 id="heading-step-13-setup-dns-optional-but-strongly-recommended">Step 13: Setup DNS (Optional but Strongly Recommended)</h2>
<p><strong>Why use a custom domain?</strong></p>
<ul>
<li><p>Easy to remember address instead of IP:port</p>
</li>
<li><p>Required for automatic HTTPS/SSL certificates</p>
</li>
<li><p>Professional appearance</p>
</li>
<li><p>Easier to share with players</p>
</li>
</ul>
<p><strong>⚠️ If you skip DNS:</strong> You can access Foundry via <a target="_blank" href="http://your_server_ip:30000"><code>http://your_server_ip:30000</code></a> (requires opening port 30000 in firewall and skipping Caddy setup).</p>
<p><strong>To set up DNS:</strong></p>
<ol>
<li><p>Go to your domain registrar or DNS provider</p>
</li>
<li><p>Add an A record pointing <a target="_blank" href="http://foundry.yourdomain.com"><code>foundry.yourdomain.com</code></a> to your VPS IP address</p>
</li>
<li><p>Wait for DNS propagation (can take up to 24 hours, usually much faster)</p>
</li>
</ol>
<h2 id="heading-step-14-access-your-foundry-instance">Step 14: Access Your Foundry Instance</h2>
<ol>
<li><p>Visit <a target="_blank" href="https://foundry.yourdomain.com"><code>https://foundry.yourdomain.com</code></a></p>
</li>
<li><p>Complete the Foundry setup wizard</p>
</li>
<li><p>Enter your license key</p>
</li>
</ol>
<h2 id="heading-uploading-files-via-sftp">Uploading Files via SFTP</h2>
<p>Use any SFTP client (FileZilla, Cyberduck, WinSCP):</p>
<ul>
<li><p><strong>Host:</strong> Your domain or server IP</p>
</li>
<li><p><strong>Port:</strong> 22</p>
</li>
<li><p><strong>Username:</strong> foundry</p>
</li>
<li><p><strong>Password:</strong> The password you set in Step 3</p>
</li>
<li><p><strong>Remote path:</strong> <code>/data</code></p>
</li>
</ul>
<p>Upload worlds, modules, and assets to the appropriate subdirectories.</p>
<h2 id="heading-useful-commands">Useful Commands</h2>
<p><strong>Check Foundry status:</strong></p>
<pre><code class="lang-bash">sudo -u foundry pm2 status
</code></pre>
<p><strong>View Foundry logs:</strong></p>
<pre><code class="lang-bash">sudo -u foundry pm2 logs foundry
</code></pre>
<p><strong>Restart Foundry:</strong></p>
<pre><code class="lang-bash">sudo -u foundry pm2 restart foundry
</code></pre>
<p><strong>Check Caddy status:</strong></p>
<pre><code class="lang-bash">sudo systemctl status caddy
</code></pre>
<p><strong>Reload Caddy config:</strong></p>
<pre><code class="lang-bash">sudo systemctl reload caddy
</code></pre>
<p><strong>View Caddy logs:</strong></p>
<pre><code class="lang-bash">sudo journalctl -u caddy -f
</code></pre>
<h2 id="heading-updating-foundry">Updating Foundry</h2>
<ol>
<li><p>Download new version as foundry user</p>
</li>
<li><p>Stop PM2: <code>pm2 stop foundry</code></p>
</li>
<li><p>Backup: <code>cp -r resources resources.backup</code></p>
</li>
<li><p>Extract new version over existing files</p>
</li>
<li><p>Start PM2: <code>pm2 start foundry</code></p>
</li>
</ol>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<p><strong>Foundry not accessible:</strong></p>
<ul>
<li><p>Check PM2 is running: <code>sudo -u foundry pm2 status</code></p>
</li>
<li><p>Check Caddy is running: <code>sudo systemctl status caddy</code></p>
</li>
<li><p>Verify DNS points to your server IP</p>
</li>
<li><p>Check firewall allows ports 80 and 443</p>
</li>
</ul>
<p><strong>Can't connect via SFTP:</strong></p>
<ul>
<li><p>Verify SSH is running: <code>sudo systemctl status sshd</code></p>
</li>
<li><p>Check firewall allows port 22</p>
</li>
<li><p>Verify foundry user password is correct</p>
</li>
</ul>
<p><strong>SSL certificate issues:</strong></p>
<ul>
<li><p>Check DNS is properly configured</p>
</li>
<li><p>View Caddy logs: <code>sudo journalctl -u caddy -f</code></p>
</li>
<li><p>Ensure ports 80 and 443 are accessible from the internet</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>