Ruby on Rails (Rails) is a full-stack web application framework built on Ruby that made convention-over-configuration and the MVC pattern mainstream in web development. Rails provides ActiveRecord (ORM), ActionController (routing and controllers), ActionView (templating with ERB/Haml), ActionMailer, ActiveJob (background jobs), ActionCable (WebSockets), and a comprehensive set of security defaults out of the box. A Rails application deployed in production uses Puma as the multi-threaded application server. Nginx handles SSL termination, static asset serving, and reverse proxying to Puma. This guide covers deploying a Rails 7 application on RHEL 9 with Puma, PostgreSQL, and Nginx, including asset precompilation and database migrations.

Prerequisites

  • Ruby 3.3 with rbenv installed on RHEL 9
  • PostgreSQL 16, Nginx, and Node.js installed (Node.js required for asset compilation)

Step 1 — Create a New Rails Application

gem install rails
rbenv rehash

cd /var/www
rails new myapp --database=postgresql --asset-pipeline=propshaft
cd myapp

Step 2 — Configure the Database

# config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  pool: 
  database: myapp_production
  username: myapp_user
  password: 
  host: 127.0.0.1
  port: 5432
psql -U postgres <<'SQL'
CREATE DATABASE myapp_production;
CREATE USER myapp_user WITH PASSWORD 'RailsDBPass123!';
GRANT ALL PRIVILEGES ON DATABASE myapp_production TO myapp_user;
SQL

RAILS_ENV=production DB_PASSWORD=RailsDBPass123! rails db:migrate

Step 3 — Configure Puma for Production

# config/puma.rb — production Puma configuration
workers Integer(ENV.fetch("WEB_CONCURRENCY") { 2 })
threads_count = Integer(ENV.fetch("RAILS_MAX_THREADS") { 5 })
threads threads_count, threads_count

bind "unix:///run/myapp.sock"
environment ENV.fetch("RAILS_ENV") { "development" }

preload_app!

on_worker_boot do
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end

Step 4 — Precompile Assets and Create systemd Service

RAILS_ENV=production SECRET_KEY_BASE=$(rails secret) rails assets:precompile
# /etc/systemd/system/myapp.service
[Unit]
Description=Rails App (Puma)
After=network.target postgresql.service

[Service]
Type=simple
User=rails
Group=rails
WorkingDirectory=/var/www/myapp
Environment=RAILS_ENV=production
Environment=DB_PASSWORD=RailsDBPass123!
EnvironmentFile=/var/www/myapp/.env
ExecStart=/home/rails/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=always

[Install]
WantedBy=multi-user.target
systemctl daemon-reload && systemctl enable --now myapp

Step 5 — Nginx Configuration

# /etc/nginx/conf.d/myapp.conf
upstream rails_app {
    server unix:///run/myapp.sock fail_timeout=0;
}

server {
    listen 80;
    server_name myapp.example.com;
    root /var/www/myapp/public;

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

    location / {
        try_files $uri/index.html $uri @rails_app;
    }

    location @rails_app {
        proxy_pass http://rails_app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }
}

Conclusion

Ruby on Rails 7 with Puma and Nginx on RHEL 9 provides a complete production-ready Ruby web stack. preload_app! in the Puma configuration uses the copy-on-write fork behaviour to pre-load the Rails application before forking workers, reducing memory usage and startup time. For zero-downtime deployments, use Puma’s phased restart feature (bundle exec pumactl phased-restart) which gradually replaces workers without dropping connections.

Next steps: How to Install Ruby with rbenv on RHEL 9, How to Configure PostgreSQL on RHEL 9, and How to Install Redis on RHEL 9.