
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
You have built the application, but now you need to deploy Ruby on Rails to production without downtime or security gaps. Many teams struggle because they treat production as an afterthought rather than a distinct engineering discipline requiring specific web server configurations, process management, and hardening. This guide provides the exact architecture and configuration I use to ship resilient Rails applications in 2026, ensuring your stack is performant, observable, and secure from day one. For foundational server security before you begin, review my initial Ubuntu server setup guide to ensure your base OS is hardened against common attacks.
How do you configure Nginx and Puma to deploy Ruby on Rails to production?
The industry-standard stack for Rails in 2026 remains Nginx as the edge server and Puma as the application server. This separation of concerns is non-negotiable for production workloads. Nginx excels at buffering slow clients, serving static assets directly from disk, terminating TLS, and enforcing rate limits. Puma focuses exclusively on executing Ruby code. Connecting them via a Unix domain socket rather than TCP localhost reduces latency and prevents port conflicts.
Configuring Puma for production concurrency
Puma's configuration dictates your application's throughput and memory footprint. In config/puma.rb, set worker counts based on available CPU cores and RAM. A common mistake is over-provisioning workers, leading to OOM kills. For a 4GB VPS, start with 2 workers and 5 threads per worker.
# config/puma.rb
max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
worker_timeout 3600 if ENV["RAILS_ENV"] == "production"
workers ENV.fetch("WEB_CONCURRENCY", 2)
preload_app!
bind "unix:///var/www/myapp/shared/tmp/sockets/puma.sock"
pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid")
plugin :tmp_restart The preload_app! directive enables copy-on-write memory sharing between workers, significantly reducing total RAM usage. Always bind to a socket path that persists across deployments, typically in a shared directory managed by Capistrano or Kamal.
Nginx reverse proxy configuration
Your Nginx config must handle upstream connections, asset caching headers, and security headers. Never expose Puma directly to the internet. The following block assumes you have obtained TLS certificates via Certbot, as detailed in my SSL setup guide.
upstream puma_backend {
server unix:/var/www/myapp/shared/tmp/sockets/puma.sock fail_timeout=0;
}
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/myapp/current/public;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control "public";
access_log off;
}
try_files $uri @puma;
location @puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
proxy_pass http://puma_backend;
}
} Note the fail_timeout=0 in the upstream block. This tells Nginx not to mark the backend as down after a single failure, which is critical during zero-downtime restarts when the socket might briefly be unavailable.
How do you manage Rails processes with systemd in production?
Relying on tools like Foreman or raw nohup scripts in production is a liability. Systemd provides automatic restarts, logging integration, resource cgroups, and dependency ordering. When you deploy Ruby on Rails to production, systemd ensures your app survives crashes and boots correctly after server reboots.
Creating a production-ready systemd unit file
Create /etc/systemd/system/rails-app.service with explicit user isolation, environment loading, and restart policies. Never run Rails as root.
[Unit]
Description=Rails Application Server
After=network.target postgresql.service redis.service
Wants=postgresql.service redis.service
[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/var/www/myapp/current
EnvironmentFile=/var/www/myapp/shared/.env.production
ExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=rails-app
# Security Hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/www/myapp/shared/tmp /var/www/myapp/shared/log
PrivateTmp=true
[Install]
WantedBy=multi-user.target The EnvironmentFile directive loads secrets outside version control. The ProtectSystem=strict combined with ReadWritePaths creates a sandbox where Puma can only write to tmp and log directories, limiting damage if compromised. After creating the file, run systemctl daemon-reload && systemctl enable --now rails-app.
What security hardening is required when deploying Rails to production?
Security cannot be bolted on after deployment. When you deploy Ruby on Rails to production, you must address secrets management, dependency vulnerabilities, and runtime isolation. Compliance frameworks like SOC 2 and ISO 27001 require evidence of these controls.
- Secrets Management: Never commit credentials to Git. Use
ENVvariables injected via systemd EnvironmentFile, AWS Secrets Manager, or HashiCorp Vault. Rotate database passwords quarterly. - Dependency Scanning: Run
bundle auditin CI to catch known CVEs. Integrate SCA tools like Trivy or Dependabot. For deeper pipeline security, see my article on shifting security left in CI/CD. - Runtime Isolation: Run Puma under a dedicated non-root user. Apply filesystem restrictions via systemd. Disable unused gems in production groups.
- TLS Enforcement: Force HTTPS everywhere. Set
config.force_ssl = trueinproduction.rbto enable HSTS and secure cookies. - Database Access: Restrict PostgreSQL/MySQL to accept connections only from the app server IP or Unix socket. Never expose DB ports to 0.0.0.0.
How does containerized vs bare-metal Rails deployment compare in 2026?
The choice between containers and bare metal depends on team size, compliance needs, and operational maturity. Both are valid for deploying Ruby on Rails to production, but they optimize for different outcomes.
| Criteria | Bare Metal / VPS (Kamal/Capistrano) | Containerized (Docker/Kubernetes) |
|---|---|---|
| Operational Complexity | Low — direct SSH, simple tooling | High — requires registry, orchestration, networking |
| Cost Efficiency | High — no container overhead, smaller instances | Moderate — K8s control plane adds baseline cost |
| Scaling Speed | Minutes — manual or script-based provisioning | Seconds — HPA/VPA autoscaling |
| Compliance Evidence | Manual — SSH logs, config audits | Automated — image SBOMs, policy-as-code |
| Best For | SMBs, solo devs, Nepal-based startups on budget | Scale-ups, multi-region, strict compliance |
For most teams starting out, especially in cost-sensitive markets like Nepal, bare-metal deployment with Kamal offers the best balance of simplicity and professionalism. Containers become justified when you need auto-scaling, multi-region failover, or standardized platform engineering across multiple services.
How do you monitor and maintain a production Rails deployment?
Deployment is not complete without observability. You cannot fix what you cannot measure. Configure structured logging in JSON format for machine parsing, and expose health endpoints for load balancer checks. Understanding the difference between metrics, logs, and traces is essential; my guide on observability signals compared breaks this down specifically for Rails applications.
Set up monitoring for the four golden signals: latency, traffic, errors, and saturation. Use Prometheus to scrape Puma's built-in stats endpoint (/metrics) and Grafana for visualization. Configure alerts for error rates exceeding 1% and p99 latency above 2 seconds. Automate log rotation to prevent disk exhaustion, and test backup restores monthly. Production readiness means assuming failure will happen and ensuring you can detect and recover from it within minutes, not hours.
Next steps for your Rails production deployment
When you deploy Ruby on Rails to production using this Nginx-Puma-systemd stack, you gain a foundation that scales from first customer to millions of requests. Start with bare metal if you are lean; graduate to containers when complexity demands it. Audit your secrets, enforce TLS, and wire up monitoring before announcing launch. If your team needs hands-on support architecting or hardening a Rails production environment, reach out through my contact page to discuss your specific infrastructure requirements.