Deploying Gatsby on Vultr comes down to one early decision: serve the built public/ directory from a small compute instance with Nginx or Caddy, or push it to Vultr Object Storage and let a bucket do the work. A compute instance costs a few dollars a month and gives you full control over headers, redirects and TLS. Object Storage removes the server entirely. Both work, and the choice mostly depends on whether you want to patch a box.
gatsby build in GitHub Actions rather than on the server, and rsync public/ to /var/www/site. Caddy fetches a Let’s Encrypt certificate automatically. Use Vultr Object Storage instead if you want no server at all and can live with less control over headers.Gatsby produces a fully static site. Once gatsby build finishes, everything in public/ is HTML, JavaScript, CSS and images, with no runtime dependency on Node unless you use Deferred Static Generation or Server Side Rendering. That means the hosting requirement is modest and the interesting engineering is in the build pipeline, not the server.
A realistic note on Gatsby in 2026
Before you commit, be clear about what you are adopting. Netlify acquired Gatsby in 2023 and development has slowed considerably since. The latest published release on npm is in the 5.x line and releases now arrive months apart rather than weekly. Security patches still land, and existing sites keep building, but the framework is not where new investment is going.
If you are starting fresh rather than deploying something you already have, weigh that against the alternatives. For a site that is genuinely static, the deployment story here is identical for any static site generator, and the same Vultr instance will serve output from anything. The official Gatsby deployment documentation is still the right reference for adapter and SSR specifics, and Vultr publishes its own product documentation for the control panel steps.
Choosing between compute and Object Storage
| Factor | Cloud Compute instance | Vultr Object Storage |
|---|---|---|
| Entry cost | Regular Performance starts at $2.50 per month for 1 vCPU and 0.5 GB with an IPv6 only address, $5.00 for the 1 GB IPv4 tier | Subscription based, billed with included storage and transfer |
| TLS certificate | Let’s Encrypt, automatic with Caddy | Needs a CDN in front for a custom domain on HTTPS |
| Custom headers and redirects | Full control | Limited |
| Maintenance | You patch the OS | None |
| Best for | Sites needing redirects, auth, or an API on the same host | Pure static output, high traffic, no server appetite |
The rest of this guide takes the compute route, because it is the one where the details matter. The Object Storage route is a bucket, an s3cmd sync and a CDN, and there is not much to get wrong.
Create and harden the Vultr instance
In the Vultr control panel choose Products, Compute, Deploy Server. Pick Cloud Compute, a region close to your audience, and the latest Ubuntu LTS image. Add your SSH key during deployment rather than using a password. Then do the standard first ten minutes.
ssh root@YOUR_SERVER_IP
adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy/
apt update && apt upgrade -y
apt install -y ufw fail2ban
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# Disable password and root SSH login
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshPoint an A record for your domain at the instance IP before the next step, because Caddy will try to issue a certificate the moment it starts and needs DNS to already resolve.
Serve the site with Caddy
Caddy is the shorter path here. It obtains and renews Let’s Encrypt certificates with no configuration, and its default settings for a static site are already correct.
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy
mkdir -p /var/www/site
chown -R deploy:deploy /var/www/siteReplace /etc/caddy/Caddyfile with this.
example.com, www.example.com {
root * /var/www/site
encode zstd gzip
file_server
@assets path /static/* /page-data/* *.js *.css *.woff2
header @assets Cache-Control "public, max-age=31536000, immutable"
@html path *.html /
header @html Cache-Control "public, max-age=0, must-revalidate"
handle_errors {
rewrite * /404.html
file_server
}
}Reload with systemctl reload caddy. If you would rather use Nginx, the equivalent server block needs try_files $uri $uri/ $uri/index.html =404; and a separate certbot run, plus the same two cache rules. The caching split matters either way: Gatsby fingerprints asset filenames but not HTML, so HTML must revalidate or visitors keep the old page shell.
page-data.json files as immutable if you serve them from a path Gatsby does not fingerprint. A stale page data file against a fresh HTML shell produces a site that renders blank on navigation and works fine on a hard reload, which is a miserable bug to chase.Build in CI, not on the server
Running gatsby build on a 1 GB instance will fail on any site with a meaningful number of images. Sharp image processing is memory hungry and the Gatsby data layer holds a lot in memory during the query phase. Build in GitHub Actions and ship only the output.
name: Deploy to Vultr
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx gatsby build
env:
NODE_OPTIONS: --max-old-space-size=4096
CI: true
- name: Install SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.VULTR_HOST }} >> ~/.ssh/known_hosts
- name: Sync public to server
run: |
rsync -avz --delete --checksum \
-e "ssh -i ~/.ssh/id_ed25519" \
public/ deploy@${{ secrets.VULTR_HOST }}:/var/www/site/Pin the Node version explicitly. Gatsby 5 is sensitive to Node major versions and an unpinned latest will eventually break a build with no code change on your side. Node 22 and Node 24 are both maintained release lines, and either is a safe target.
--checksum flag on rsync compares file contents rather than timestamps. Gatsby rewrites files on every build even when their content is identical, so without it you re upload the entire site each deploy.Verify and monitor
After the first deploy, check the three things that actually break. Confirm HTTPS resolves with a valid certificate, confirm a deep route loads on a fresh browser session, and confirm the 404 page renders for a nonsense path. Then check your headers.
curl -sI https://example.com/ | grep -i 'cache-control\|content-encoding'
curl -sI https://example.com/static/some-asset.js | grep -i cache-control
curl -so /dev/null -w '%{http_code}\n' https://example.com/does-not-existOnce the site is live, a small VPS is worth watching. If you are running several of these, the guides on where to host Grafana and where to host Prometheus cover a lightweight monitoring setup that fits on the same class of instance. If you plan to add a dynamic application to the same server later, our walkthrough on deploying Laravel on Vultr reuses this exact instance setup, and the notes on installing Node.js on cloud hosting cover the runtime side if you move to SSR.
Troubleshooting
Build fails with “JavaScript heap out of memory”. Raise the Node heap with NODE_OPTIONS=--max-old-space-size=4096, as in the workflow above. If it still fails, the cause is usually image processing volume. Reduce the number of generated image variants in your gatsby-plugin-image configuration.
Site loads but navigation between pages shows a blank screen. Mismatched cache state. The HTML shell is new and the page-data JSON is stale, or the reverse. Purge any CDN in front, verify the HTML no cache header is applied, and redeploy with rsync --delete so orphaned files are removed.
Caddy fails to start with a certificate error. DNS is not resolving to the instance yet, or port 80 is blocked. Let’s Encrypt validates over HTTP even for an HTTPS certificate. Confirm with dig +short example.com and check ufw status.
Deploy succeeds but the old site is still served. You synced into the wrong directory, or Caddy’s root does not match. Run ls -la /var/www/site and check the timestamp on index.html. A common slip is rsync public instead of rsync public/, which creates /var/www/site/public.
Images 404 after moving hosts. Gatsby writes images into public/static with content hashes. If your rsync excludes or your .gitignore filtered that directory, the references survive but the files do not. Sync the whole public/ tree without exclusions.
Frequently asked questions
Can Vultr host a Gatsby site?
Yes. A Gatsby build is static output, so any Vultr Cloud Compute instance running Nginx or Caddy serves it. Vultr Object Storage works too if you put a CDN in front for a custom domain over HTTPS. Neither requires Node on the server for a standard static build.
How big an instance do I need?
For serving only, the smallest Regular Performance plan is plenty because you are handing out static files. If you insist on building on the server, budget at least 4 GB of memory and expect long builds. Building in CI and syncing the output avoids the question entirely.
Do I need Node.js installed on the Vultr server?
Not for a standard static build. You only need Node on the server if you use Gatsby’s Server Side Rendering or Deferred Static Generation, which require gatsby serve or an adapter running as a process. Most Gatsby sites use neither.
Is Gatsby still maintained?
It receives updates, but at a much slower pace than during its peak. Netlify has owned the project since 2023 and release cadence has dropped substantially. Existing sites continue to build and deploy normally. Pin your Node version and your dependencies and a working site stays working.
How do I set up automatic HTTPS?
Use Caddy and it handles it with no configuration beyond putting your domain in the Caddyfile. It requests and renews Let’s Encrypt certificates automatically. With Nginx, run certbot --nginx -d example.com and certbot installs a systemd timer for renewal.
Wrapping up
The Vultr side of this is straightforward and cheap. A small compute instance, Caddy for TLS and headers, and an rsync target is all a Gatsby site needs, and it will comfortably serve far more traffic than most sites ever see. Object Storage is the alternative when you would rather not own a server at all.
The part worth spending your attention on is the build pipeline. Keep gatsby build in CI where memory is not rationed, pin Node explicitly, and use --checksum so deploys stay fast. Do that and your deployment stays boring even as the framework itself moves more slowly than it once did.
