Websites & apps

Deploy a Ruby on Rails app on a VPS with Puma and Nginx

Deploy Rails to an Ubuntu VPS: install Ruby with rbenv, configure the database and credentials, precompile assets, run Puma under systemd and put Nginx with SSL in front.

AdvancedLinux VPS3 min read

Rails apps run on an application server, usually Puma, with Nginx in front for HTTPS and static files. This guide shows how to deploy Ruby on Rails on a VPS manually, which is also what tools like Kamal or Capistrano automate.

Before you start

  • An Ubuntu LTS VPS with 2 GB RAM or more and a deploy user with sudo.
  • A domain pointing to the VPS.
  • Your app in Git, with config/master.key available to you (it is not committed).

Install build tools and Ruby

sudo apt update && sudo apt install -y git curl build-essential libssl-dev libreadline-dev zlib1g-dev libyaml-dev libpq-dev nginx postgresql

Install rbenv and the Ruby version from your app's .ruby-version, as the deploy user:

git clone https://github.com/rbenv/rbenv.git ~/.rbenv && git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'eval "$(~/.rbenv/bin/rbenv init - bash)"' >> ~/.bashrc && source ~/.bashrc
rbenv install 3.3.6 && rbenv global 3.3.6 && gem install bundler

Use the version your app requires instead of the example. Install Node.js too if your app uses a JavaScript bundler.

Create the database

sudo -u postgres createuser -P deploy
sudo -u postgres createdb -O deploy myapp_production

Get the code and configure it

git clone git@github.com:you/myapp.git ~/myapp && cd ~/myapp
bundle config set --local deployment true && bundle config set --local without 'development test' && bundle install

Copy your master key to the server (never commit it) and restrict it:

chmod 600 config/master.key

Set the database URL and environment in an env file, e.g. ~/myapp/.env.production, used by systemd below:

RAILS_ENV=production
DATABASE_URL=postgres://deploy:strong-password@127.0.0.1/myapp_production
RAILS_SERVE_STATIC_FILES=false
RAILS_LOG_TO_STDOUT=true

Prepare the database and assets:

set -a; . ./.env.production; set +a; bin/rails db:migrate && bin/rails assets:precompile

Run Puma with systemd

Create /etc/systemd/system/myapp.service:

[Unit]
Description=myapp Puma
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 -b tcp://127.0.0.1:3000
Restart=always

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

Tune workers and threads in config/puma.rb (WEB_CONCURRENCY, RAILS_MAX_THREADS) to your CPU and RAM.

Configure Nginx

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

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

    try_files $uri @app;

    location @app {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Nginx needs execute permission on the path to public (chmod o+x /home/deploy). Reload Nginx and add SSL with sudo certbot --nginx -d example.com. In Rails, keep config.force_ssl = true.

Deploy updates

cd ~/myapp && git pull && bundle install && set -a && . ./.env.production && set +a && bin/rails db:migrate && bin/rails assets:precompile && sudo systemctl restart myapp

Troubleshooting

  • ActiveSupport::MessageEncryptor::InvalidMessage: wrong or missing master.key for the credentials file.
  • 502 from Nginx: Puma is not running. journalctl -u myapp -n 50 shows why.
  • Assets 404: assets were not precompiled or Nginx cannot read public.
  • Blocked host error: add the domain to config.hosts in production.rb.

Rails deployment FAQ

Should I use Kamal instead?

Kamal (bundled with Rails 8) deploys Docker containers over SSH and is a great option once you are comfortable with containers.

Can Rails run on cPanel hosting?

Some cPanel servers offer Ruby app support via Passenger, but versions and gems are limited. A VPS is the dependable choice.

How many Puma workers should I use?

Roughly one worker per CPU core, with 3–5 threads each, limited by available RAM.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.