Ruby on Rails VPS Deployment Guide: Puma, Nginx, PostgreSQL and systemd
A production Rails deployment on a VPS with Puma, Nginx and PostgreSQL.
A production Rails deployment on a VPS runs the app under Puma, managed by systemd, behind Nginx, with PostgreSQL alongside and background jobs in their own process. The Rails guides cover configuration and security. Puma's deployment notes cover workers and threads. This guide ties them together on a KVM VPS.
Prerequisites
- A KVM VPS with root access. VPS 8 (4 vCPU, 8 GB, ₹1,438) is a sensible production start for an app and its database.
- Ruby installed with a version manager, and your app's Ruby version pinned.
- PostgreSQL installed and a database user created for the app.
Step 1: production configuration
- Set
RAILS_ENV=production. - Keep secrets in Rails encrypted credentials and provide the master key through an environment variable, not the repository.
- Force SSL in production so cookies are only sent over HTTPS, as the Rails security guide recommends.
Step 2: Puma workers and threads
Puma combines processes (workers) and threads. Puma's deployment documentation suggests starting with one worker per CPU core and a small number of threads per worker, then tuning from measurements.
# config/puma.rb
workers Integer(ENV.fetch("WEB_CONCURRENCY", 4))
threads_count = Integer(ENV.fetch("RAILS_MAX_THREADS", 5))
threads threads_count, threads_count
preload_app!
bind "unix:///srv/app/tmp/puma.sock"Match the database connection pool to the thread count, or requests will wait for connections.
Step 3: systemd
[Service]
User=app
WorkingDirectory=/srv/app
Environment=RAILS_ENV=production
EnvironmentFile=/srv/app/.env
ExecStart=/home/app/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=on-failureStep 4: Nginx
Proxy to the Puma socket, serve precompiled assets from public/assets directly with long cache headers, and terminate TLS in Nginx. The Rails asset pipeline guide explains precompilation.
Step 5: deploy sequence
bundle install --deployment --without development testbin/rails assets:precompilebin/rails db:migrate- Restart Puma through systemd, or use a phased restart for zero downtime.
Step 6: background jobs
Run your Active Job backend as its own systemd service so a slow job never blocks web requests, and restart it on each deploy.
Sizing
| Plan | vCPU | Memory | RAID NVMe | Transfer | Monthly |
|---|---|---|---|---|---|
| VPS 2 | 1 | 2 GB | 20 GB | 100 GB | ₹298 |
| VPS 4 | 2 | 4 GB | 40 GB | 200 GB | ₹596 |
| VPS 8 | 4 | 8 GB | 80 GB | 400 GB | ₹1,438 |
| VPS 16 | 6 | 16 GB | 160 GB | 800 GB | ₹2,399 |
| VPS 32 | 12 | 32 GB | 320 GB | 1,600 GB | ₹4,799 |
Monthly INR, excluding GST. All ten sizes, from 1 GB (₹149) to 128 GB (₹19,099), are on the VPSWala cloud VPS plans page. Every plan is KVM with RAID NVMe storage and full root access.
Ruby request handling benefits from fast single cores. For CPU-heavy apps, compare the Ryzen 9 9950X VPS plans.
VPSWala plans are unmanaged unless you agree otherwise in writing: the host platform, network and provisioning are VPSWala's job, and everything inside the VM is yours. Per the terms of service, off-server backups, control-panel licences, Windows licensing, managed administration and extra IPv4 addresses are quoted separately, and any availability commitment applies only where it is written into your quotation or agreement.
Verify the deployment
- Load the app over HTTPS and confirm HTTP redirects to HTTPS.
- Check that asset URLs return long cache headers from Nginx.
- Kill a Puma worker and confirm the master replaces it.
- Reboot the VPS and confirm Puma and the job runner return on their own.
- Run a database backup and restore it to a scratch database.
Security checklist
- Keep
config/master.keyout of the repository. - Run the app as a non-root user.
- Keep Ruby, gems and the operating system patched, and run a dependency audit regularly.
- Firewall everything except SSH, HTTP and HTTPS.
Logs and monitoring
Log to standard output and let systemd's journal collect it, or write to log/production.log with daily rotation. Track response times, error rates and Puma's backlog. A growing backlog with idle CPU usually means the database pool is too small or a slow external call is holding threads. Add an external uptime check against a lightweight health route that also touches the database.
Record the Ruby, Rails and PostgreSQL versions in the repository so the next deploy, or a rebuild on a larger plan, repeats this setup exactly.
Frequently asked questions
How much memory does a Rails app need?
Measure one Puma worker after warm-up, multiply by the number of workers, and add PostgreSQL, the job runner and the operating system.
Should PostgreSQL run on the same VPS?
For small and medium apps, yes. Move it to its own VPS when it needs its own memory or you want to scale web servers separately.
Capistrano or containers?
Both are fine. Choose one and document it so every deploy is the same.
Sources
Not sure which size?
Send the stack, get a size.
Tell us the operating system, application stack, current traffic, database size and where it hurts today. You get a sizing recommendation, the matching plan and a price.
Related
Deploying Next.js SSR on a Linux VPS: Standalone Build, PM2 & Nginx Proxy
Learn how to deploy a server-rendered Next.js application on a Linux KVM VPS using standalone build artifacts, PM2 cluster management, and Nginx.
VPS Firewall Setup with UFW and iptables: Port Hardening & Lockout Prevention
A hands-on sysadmin guide to securing a Linux cloud VPS with UFW and iptables, implementing strict ingress filtering while preventing accidental connection loss.
VPS Hosting for Bhopal: Which VPSWala Node to Pick and How to Test It
A practical routing and workload sizing guide for developers and businesses in Bhopal to evaluate VPSWala cloud nodes and verify network path stability.