Deploy Rails on Vultr VPS: Production Setup Guide (2026)

This post contains affiliate links. If you purchase through our links, we may earn a commission at no extra cost to you.

Deploy Rails on Vultr VPS: Production Setup Guide (2026)
Deploy Rails on Vultr VPS: Production Setup Guide (2026)

Most “deploy Rails” tutorials online are either written for Ubuntu 20.04, assume you already have Redis and Sidekiq running, or skip straight past the one mistake that will actually break your first deploy. If you’re trying to deploy Rails on Vultr and want something that works the first time, you need three things sorted before you touch a terminal: the right plan size, the right stack for Rails 8, and a plan for the secret_key_base error that catches almost everyone. On a small Vultr box, the difference between “it works” and “it OOM-kills mid-deploy” usually comes down to decisions made before the first apt install.

Architecture diagram showing Nginx reverse proxy, Puma app server on a Unix socket, and PostgreSQL 16 running together on a single Vultr Cloud Compute instance
Architecture diagram showing Nginx reverse proxy, Puma app server on a Unix socket, and PostgreSQL 16 running together on a single Vultr Cloud Compute instance
🛠️ Free tools for this guide: Systemd Service Generator (generate the .service unit that keeps your Rails app running)

Choosing the Right Vultr Plan to Deploy Rails

Skip the guesswork — here’s what actually fits a Rails stack in production:

Vultr PlanSpecsPriceGood For
Cloud Compute (Regular)1 vCPU, 1 GB RAM, 25 GB SSD$5–6/moPortfolio or MVP only — requires a swap file
Cloud Compute High Performance1 vCPU, 2 GB RAM, 50 GB NVMe, 2 TB bandwidth$12/moThe production sweet spot for a single Rails app
Cloud Compute High Performance2 vCPU, 4 GB RAM, 100 GB NVMe$24/moApps running background jobs (Solid Queue) alongside web traffic

Prices last checked: 2026-07-05

The $12/month, 1 vCPU / 2 GB High Performance instance is where I’d start almost every real Rails deployment. Puma running two or three workers, PostgreSQL, and Nginx all fit comfortably in 2 GB, and bundle install plus rails assets:precompile — both memory-hungry — won’t fight each other for RAM during a deploy.

The $5–6/month 1 GB plan is tempting, and it does work, but only if you add a 1–2 GB swap file first. Without it, bundle install or asset precompilation gets OOM-killed partway through, which is a confusing failure to debug on your first deploy. It’s fine for a low-traffic portfolio site. It’s not fine the moment you add a second Puma worker or a background job processor.

If your app runs Solid Queue jobs alongside web traffic — anything from email sending to report generation — step up to the $24/month, 2 vCPU / 4 GB plan. Solid Queue polls the same PostgreSQL database your web workers use, and the extra headroom keeps job processing from starving Puma of memory during traffic spikes. New Vultr accounts get $300 in free credit valid for 30 days, which comfortably covers a month or two of testing at any of these tiers before you pay anything out of pocket.

🎁 Get $300 free credit when you sign up (valid for 30 days, limited-time offer) — Claim your credit →

For a deeper breakdown of every Vultr tier and where the hidden costs are, see our full Vultr pricing breakdown.

Traditional Deployment vs. Kamal 2: Which Should You Use?

Rails 8 changed the default answer to “how do I deploy this?” New apps ship with Kamal 2 out of the box — it builds a Docker image, pushes it to a registry, and pulls it onto your server over SSH, swapping traffic to the new container only after it passes health checks. A first deploy takes 3–5 minutes; after that, Docker layer caching gets you down to 60–90 seconds with zero dropped connections.

That’s genuinely good, and if you want it, it’s the modern path. But it also adds a Docker registry dependency and another layer to debug when something goes wrong, and it starts to strain once you’re running more than one server. For a single cheap VPS — which is what almost everyone searching for how to deploy Rails on Vultr is actually running — the traditional systemd + Puma + Nginx setup is still the more common choice, and it’s what this guide teaches. No registry, no extra daemon, and when something breaks at 2am, you’re reading a systemd log instead of untangling a container.

If zero-downtime deploys matter more to you than simplicity, or you’re already comfortable with Docker, Kamal is worth adopting — the traditional approach below still gets you 95% of the way there with pumactl phased-restart handling graceful reloads. This guide covers the traditional route in full; treat Kamal as the upgrade path once your deploy frequency justifies the extra moving part.

Prerequisites Before You Start

  • A Vultr account with a Cloud Compute instance provisioned (Ubuntu 24.04 LTS)
  • A domain name with an A record pointing at your server’s IP
  • A Rails app in a Git repository, with config/master.key available locally (never committed)
  • Basic comfort with SSH and the command line

Step 1: Spin Up and Secure Your Vultr Server

Deploy an Ubuntu 24.04 LTS instance on the High Performance plan you picked above, then lock it down before installing anything else — create a non-root deploy user, disable root SSH login, and enable the firewall.

adduser deploy
usermod -aG sudo deploy
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

💡 Tip: Prefer a visual approach? You can build these rules with our free Firewall Rule Builder — it generates ufw and iptables commands right in your browser.

For the full walkthrough on locking down SSH keys, fail2ban, and Vultr’s cloud firewall rules, see our step-by-step Vultr firewall guide.

Step 2: Install Ruby with rbenv

rbenv is still the most common Ruby version manager in production tutorials, and it’s the lowest-overhead option for a box that only runs Ruby. (If you’re managing multiple languages on the same server, mise is a faster, Rust-based alternative worth a look — but for a Rails-only VPS, rbenv is simpler and just as reliable.)

sudo apt update && sudo apt install -y git curl libssl-dev libreadline-dev zlib1g-dev \
  autoconf bison build-essential libyaml-dev libgdbm-dev libncurses5-dev libffi-dev

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc

git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 3.3.6
rbenv global 3.3.6
gem install bundler

Ruby 3.3.x is the stable baseline most Rails 8 apps are pinned to in 2026 — use whatever patch version matches your app’s .ruby-version file.

Terminal output showing rbenv install completing and rbenv global set to Ruby 3.3.6
Terminal output showing rbenv install completing and rbenv global set to Ruby 3.3.6

Step 3: Install and Tune PostgreSQL 16

sudo apt install -y postgresql-16 postgresql-contrib libpq-dev
sudo -u postgres createuser -s deploy
sudo -u postgres createdb myapp_production -O deploy

The default PostgreSQL configuration assumes a much bigger server than a $12/month VPS. On a 2 GB box, drop shared_buffers in /etc/postgresql/16/main/postgresql.conf to around 128 MB — the out-of-the-box default overshoots what’s available once Puma and Nginx take their share, and you’ll see memory pressure show up as random slowdowns rather than a clean error.

shared_buffers = 128MB
effective_cache_size = 512MB
work_mem = 4MB

Restart with sudo systemctl restart postgresql after editing. For a complete self-hosted Postgres setup including backups and access control, our PostgreSQL on Vultr guide goes deeper than the tuning notes here.

Step 4: Prepare Your Rails App for Production

Clone your app to the server and set up the production environment file:

git clone [email protected]:you/myapp.git /home/deploy/myapp
cd /home/deploy/myapp
bundle install --deployment --without development test

The most common first-deploy failure: secret_key_base / RAILS_MASTER_KEY mismatch. Rails decrypts config/credentials.yml.enc using ENV["RAILS_MASTER_KEY"] first, then falls back to config/master.key. If the key on your server doesn’t exactly match the one your credentials were encrypted with, the app crashes on boot with a decryption error — not a helpful one either. Copy your local config/master.key to the server (never commit it to Git), or set RAILS_MASTER_KEY in the systemd EnvironmentFile you’ll create in the next step. If you renamed an environment-specific key file, Rails’ lookup breaks even with a correct env var set, so keep the default naming unless you have a specific reason not to.

Run asset precompilation once your key is in place:

RAILS_ENV=production bundle exec rails assets:precompile
RAILS_ENV=production bundle exec rails db:migrate

On the 1 GB budget plan, this is the exact step that OOM-kills without swap. Add one before you get here:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo sysctl vm.swappiness=10

Step 5: Run Puma as a systemd Service

Puma binding to a Unix socket (rather than a TCP port) is the lowest-latency option when Nginx sits directly in front of it on the same box. Create /etc/systemd/system/puma.service:

[Unit]
Description=Puma HTTP Server for myapp
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/myapp
EnvironmentFile=/home/deploy/myapp/.env.production
ExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now puma

Set your worker and thread counts in config/puma.rb based on what the box can actually handle — on a 1 vCPU / 2 GB instance, 2 workers with 3–5 threads each is a sane starting point; push higher on the 4 GB plan. When you need to deploy an update without dropping in-flight requests, use Puma’s phased restart instead of a hard restart:

sudo systemctl kill -s SIGUSR1 puma

A plain systemctl restart puma drops every open connection — fine for a personal project, not for anything with real traffic.

systemd status output showing the Puma service active and running, bound to a Unix socket
systemd status output showing the Puma service active and running, bound to a Unix socket

Step 6: Configure Nginx as a Reverse Proxy

sudo apt install -y nginx

/etc/nginx/sites-available/myapp:

upstream puma_myapp {
  server unix:/home/deploy/myapp/tmp/sockets/puma.sock fail_timeout=0;
}

server {
  listen 80;
  server_name example.com;
  root /home/deploy/myapp/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @puma;

  location @puma {
    proxy_pass http://puma_myapp;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

💡 Tip: Want a head start on the config? Our free Nginx Config Generator builds a working server block you can adapt to your setup.

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Nginx serves precompiled, fingerprinted assets directly from public/assets — Puma never even sees those requests, which is exactly the point of precompiling in the first place.

Step 7: Secure Your Site with Certbot

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Certbot edits your Nginx config in place and sets up auto-renewal via a systemd timer — verify it with sudo certbot renew --dry-run.

Browser address bar showing a valid HTTPS padlock for the deployed Rails app domain
Browser address bar showing a valid HTTPS padlock for the deployed Rails app domain

Skipping Redis: Rails 8’s Solid Queue, Cache, and Cable

This is the part most 2026 tutorials still get wrong because they were written against Rails 6 or 7 assumptions. Rails 8 ships with the “Solid Trio” — Solid Queue, Solid Cache, and Solid Cable — database-backed replacements for what used to require a separate Redis instance plus Sidekiq. Solid Queue handles background jobs by polling PostgreSQL directly, Solid Cache stores your cache in the same database, and Solid Cable does the same for Action Cable pub/sub, checking for new messages roughly every 100ms.

For most small-to-medium Rails apps, that means one less service to install, secure, and monitor on a VPS that’s already tight on RAM — a real win on a $12/month box where every process competing for memory matters. Basecamp and HEY both run this combination in production, so it’s not an experimental choice. If you’re on an older Rails version, or you’re running job volumes high enough that database polling becomes a bottleneck, Redis + Sidekiq is still the right fallback — just don’t reach for it by default anymore. Enable Solid Queue as a Puma plugin or a separate process depending on your config/queue.yml, and if you go that route, size up to the $24/month plan so the job runner isn’t fighting Puma for memory during traffic spikes.

Common Deployment Mistakes (and How to Avoid Them)

  • Skipping swap on the 1 GB plan. bundle install and assets:precompile are the two commands most likely to get OOM-killed on a budget instance — add swap before you run either.
  • Exposing Puma directly to the internet. Never run rails server in production or open Puma’s port to the public — Nginx handles static files and connection management far more efficiently.
  • Leaving PostgreSQL at default tuning. The out-of-the-box shared_buffers value assumes a bigger server than a budget VPS provides.
  • Hard-restarting Puma on every deploy. Use phased restarts, or move to Kamal if zero-downtime deploys are a hard requirement.
  • Losing track of config/master.key. Keep a secure backup outside of Git — losing it means losing access to every encrypted credential in credentials.yml.enc.

A Production Rails Setup on the $12 Plan

For a real production app, deploy Rails on Vultr using the $12/month High Performance plan, rbenv with Ruby 3.3, Puma bound to a Unix socket, Nginx in front, and PostgreSQL 16 tuned down for a small box — skip Redis entirely unless your job volume actually demands it. That combination gets you a stable, debuggable production setup without adding services you don’t need yet.

If you’re just testing an idea, the $5–6/month plan with a swap file will get you there — just budget the upgrade the moment you add background jobs or a second Puma worker. And if zero-downtime deploys matter enough to justify learning Docker, Kamal 2 is a legitimate next step once the traditional setup above starts feeling limiting. Grab a Vultr High Performance instance with the $300 free credit for new accounts, and you can have this entire stack running before the trial even ends.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top