Scaling a Ruby on Rails application across multiple VPS servers lets you handle more traffic, improve fault tolerance, and separate concerns like web serving, background jobs, and database load. This guide walks you through a practical, step-by-step approach to distributing your Rails app across several VPS instances, covering everything from initial setup to common pitfalls and troubleshooting.
What You’ll Need
- Two or more VPS servers (any provider) with Ubuntu 22.04 or similar Linux distribution.
- Root or sudo access to each server.
- A Rails application that is ready for production (database migrations, assets compiled, etc.).
- Basic familiarity with the command line, SSH, and Rails deployment concepts.
- Optional but recommended: a load balancer (can be a separate VPS or a cloud provider’s load balancer service).
- Domain name pointing to your load balancer or server IP.
Step-by-Step
- Design your architecture. Decide which components go where. A common setup is:
- Two or more application servers running Puma (or Unicorn) and Nginx.
- One dedicated database server (PostgreSQL or MySQL) – or use a managed database service.
- One Redis server for caching, sessions, and background job queues (Sidekiq).
- One load balancer (HAProxy, Nginx, or a cloud LB) that distributes HTTP traffic.
- Set up the database server. On your database VPS, install PostgreSQL (or MySQL), configure it to listen on the private network IP, and create a user and database for your Rails app. Ensure the database server is firewalled to only accept connections from your app servers’ IPs.
- Set up Redis. On a dedicated Redis VPS (or the same as the database if you’re on a budget), install Redis, bind it to the private IP, and set a strong password. Configure Rails to use Redis for cache store and Sidekiq.
- Prepare your Rails application. Update
config/database.ymlto point to the database server’s private IP. Updateconfig/cache_storeandconfig/sidekiq.ymlto point to the Redis server. Make sure your app is using a shared session store (like Redis or a database) because cookies alone won’t work across multiple servers if you need server-side sessions. - Deploy the app to the first application server. Clone your repository, install dependencies with
bundle install --without development test, precompile assets, and run database migrations. Use a process manager like systemd or Foreman to keep Puma running. Configure Nginx to reverse proxy to Puma. - Deploy to the second application server. Repeat the same steps on the second app server. Ensure the code is identical (use the same commit). Run migrations only once – from the first server or a dedicated migration task.
- Set up the load balancer. On your load balancer VPS, install Nginx or HAProxy. Configure it to forward HTTP traffic to both app servers’ private IPs on the Puma port (e.g., 3000). Enable sticky sessions if you rely on server-side sessions, though Redis sessions make this less necessary. Add SSL termination here if you have a domain.
- Configure background jobs. On one of the app servers (or a dedicated worker server), start Sidekiq with the same Redis configuration. Ensure your
config/sidekiq.ymlhas the correct Redis URL. Monitor that jobs are processed. - Update DNS. Point your domain’s A record to the load balancer’s public IP. Wait for propagation.
- Test everything. Visit your site, perform a login, trigger a background job, and verify that both app servers are receiving traffic (check logs). Use
aborwrkto simulate load and confirm the load balancer distributes requests.
Common Mistakes
- Pointing app servers to a local database. Forgetting to update
database.ymlafter moving the database to a separate server is a classic error. - Not using a shared session store. If you keep the default cookie store, users may be logged out randomly when requests go to different servers.
- Running migrations on every server. Migrations should run once, not on each app server, or you’ll get conflicts.
- Ignoring firewall rules. Leaving database and Redis ports open to the public internet is a security risk. Restrict them to private IPs.
- Not precompiling assets on each server. If you deploy with Capistrano or similar, ensure assets are compiled on each server or served from a shared location.
Tips
- Use a configuration management tool like Ansible or Chef to keep servers in sync.
- Consider using a managed database and Redis service to reduce operational overhead.
- Set up monitoring (e.g., New Relic, Prometheus) to track performance and errors across servers.
- Use environment variables for sensitive data like database passwords and Redis URLs.
- For zero-downtime deployments, use a deployment tool like Capistrano that handles symlinks and restarts gracefully.
Troubleshooting
- Users get 502 Bad Gateway from the load balancer. Check that Puma is running on each app server and that the load balancer can reach the private IPs. Test with
curlfrom the load balancer to each app server’s IP:port. - Database connection refused. Verify the database server is listening on the correct IP and port, and that the firewall allows connections from app servers. Check credentials in
database.yml. - Sidekiq jobs not processing. Ensure Redis is reachable from the worker server, and that Sidekiq is running with the correct environment (production). Check logs with
journalctl -u sidekiq. - Session loss after login. Confirm you’ve switched to a shared session store (Redis or database). Clear cookies and test again.
- Assets not loading. Precompile assets on each server or use a CDN. Check that
config.assets.compileis false in production.
Frequently Asked Questions
Can I scale without a load balancer?
Yes, but you’d need to use DNS round-robin, which is less reliable and doesn’t handle server failures well. A load balancer is recommended for production.
Do I need a separate database server?
For small scale, you can run the database on one of the app servers. But as traffic grows, a dedicated database server improves performance and isolation.
How do I handle file uploads?
Use a shared storage service like Amazon S3 or a dedicated file server. Local disk storage won’t work across multiple servers.
What about WebSockets (Action Cable)?
Action Cable requires a shared Redis adapter. Configure config/cable.yml to use Redis, and ensure the load balancer supports WebSocket upgrades.
Can I add more app servers later?
Yes, just deploy the same code to a new server, add it to the load balancer’s upstream block, and reload Nginx/HAProxy.
Is it better to use Docker for this?
Docker can simplify deployment and ensure consistency, but it adds complexity. For a simple multi-VPS setup, traditional deployment works fine.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Aquarium Gallons Calculator: Volume in US or Imperial
- Waves Later Codes? Meaning & Context
- Apex Legends Zoom In/Out: How to Change FOV
- Recover exFAT Partition: Full Guide
- Compare Two Directories from the Command Line
- DNS Over HTTPS: What It Is & How It Works