{"id":4475,"date":"2026-09-12T17:34:58","date_gmt":"2026-09-12T12:04:58","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/how-to-deploy-ruby-on-rails-puma-nginx-ubuntu\/"},"modified":"2026-09-17T11:23:06","modified_gmt":"2026-09-17T05:53:06","slug":"how-to-deploy-ruby-on-rails-puma-nginx-ubuntu","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/how-to-deploy-ruby-on-rails-puma-nginx-ubuntu\/","title":{"rendered":"How to Deploy Ruby on Rails 7\/8 with Puma and Nginx on Ubuntu 24.04"},"content":{"rendered":"<p>Deploying a modern <strong>Ruby on Rails 7 or Rails 8<\/strong> web application in production requires a resilient multi-tier infrastructure. While managed platforms offer quick deployments, hosting Rails directly on an enterprise <a href=\"https:\/\/cpanelfree.com\/\">Linux cloud VPS<\/a> delivers unmatched cost efficiency, compute predictability, and granular architectural control. In this definitive guide, we walk through setting up a complete production stack: <strong>rbenv<\/strong>, <strong>PostgreSQL<\/strong>, <strong>Puma clustered application server<\/strong>, <strong>systemd daemon management<\/strong>, and <strong>Nginx HTTP\/2 reverse proxying<\/strong> with automated SSL encryption.<\/p>\n<p><!-- more --><\/p>\n<h2>1. Rails Production Architecture Overview<\/h2>\n<p>Modern Rails applications handle concurrent traffic through a battle-tested architecture that isolates static asset delivery from dynamic Ruby process execution:<\/p>\n<ul>\n<li><strong>Client Layer:<\/strong> Public HTTPS requests enter through port 443 with TLS 1.3 termination.<\/li>\n<li><strong>Nginx Reverse Proxy:<\/strong> Directly serves static assets (precompiled CSS, JavaScript, WebP images from <code>public\/<\/code>) with aggressive caching headers, and forwards dynamic requests through a Unix domain socket.<\/li>\n<li><strong>Puma Clustered Server:<\/strong> Manages worker processes and threads using Ruby&#8217;s concurrent runtime, executing ActiveRecord queries and template rendering.<\/li>\n<li><strong>PostgreSQL:<\/strong> Persistent relational database running with connection pooling and optimized buffers.<\/li>\n<li><strong>Redis &amp; Solid Queue \/ Sidekiq:<\/strong> Asynchronous background job processing for transactional emails, webhooks, and heavy calculations.<\/li>\n<\/ul>\n<h2>2. Server Prerequisites and Base Tooling<\/h2>\n<p>Update your Ubuntu 24.04 LTS operating system and install the essential build toolchains required to compile Ruby native extensions:<\/p>\n<pre><code>sudo apt update &amp;&amp; sudo apt upgrade -y\nsudo apt install -y git curl libssl-dev libreadline-dev zlib1g-dev autoconf     bison build-essential libyaml-dev libreadline-dev libncurses5-dev     libffi-dev libgdbm-dev nginx postgresql postgresql-contrib libpq-dev redis-server<\/code><\/pre>\n<h3>Configuring Dedicated Deployment User<\/h3>\n<p>Never run Ruby on Rails applications under the <code>root<\/code> account. Create an isolated system user named <code>deploy<\/code>:<\/p>\n<pre><code>sudo adduser --disabled-password --gecos \"\" deploy\nsudo usermod -aG sudo deploy\nsudo su - deploy<\/code><\/pre>\n<h3>Installing Ruby via rbenv<\/h3>\n<p>Using <code>rbenv<\/code> ensures clean version management without interfering with system-level packages:<\/p>\n<pre><code>git clone https:\/\/github.com\/rbenv\/rbenv.git ~\/.rbenv\necho 'export PATH=\"$HOME\/.rbenv\/bin:$PATH\"' &gt;&gt; ~\/.bashrc\necho 'eval \"$(rbenv init -)\"' &gt;&gt; ~\/.bashrc\nsource ~\/.bashrc\n\ngit clone https:\/\/github.com\/rbenv\/ruby-build.git ~\/.rbenv\/plugins\/ruby-build\nrbenv install 3.3.5\nrbenv global 3.3.5\ngem install bundler --no-document<\/code><\/pre>\n<h2>3. Database Provisioning &amp; Environment Configuration<\/h2>\n<p>Switch to the PostgreSQL console to provision a secure database user and production schema:<\/p>\n<pre><code>sudo -u postgres psql\n\nCREATE ROLE deploy_user WITH LOGIN PASSWORD 'SuperSecretStrongDBPass2026!';\nALTER ROLE deploy_user CREATEDB;\nCREATE DATABASE rails_production OWNER deploy_user;\n\\q<\/code><\/pre>\n<p>Clone your Rails application repository into <code>\/var\/www\/rails_app<\/code> and configure production credentials or an encrypted <code>.env<\/code> file:<\/p>\n<pre><code>cd \/var\/www\/rails_app\nbundle config set --local deployment 'true'\nbundle config set --local without 'development test'\nbundle install\n\n# Precompile assets and migrate database\nRAILS_ENV=production bundle exec rails db:migrate\nRAILS_ENV=production bundle exec rails assets:precompile<\/code><\/pre>\n<h2>4. Configuring Clustered Puma Application Server<\/h2>\n<p>Create or update <code>config\/puma.rb<\/code> to optimize thread pools and process clustering matching your VPS CPU cores:<\/p>\n<pre><code># config\/puma.rb\nmax_threads_count = ENV.fetch(\"RAILS_MAX_THREADS\") { 5 }\nmin_threads_count = ENV.fetch(\"RAILS_MIN_THREADS\") { max_threads_count }\nthreads min_threads_count, max_threads_count\n\nworkers ENV.fetch(\"WEB_CONCURRENCY\") { 2 }\nbind \"unix:\/\/\/var\/www\/rails_app\/tmp\/sockets\/puma.sock\"\n\nenvironment ENV.fetch(\"RAILS_ENV\") { \"production\" }\npidfile ENV.fetch(\"PIDFILE\") { \"\/var\/www\/rails_app\/tmp\/pids\/puma.pid\" }\nstate_path \"\/var\/www\/rails_app\/tmp\/pids\/puma.state\"\n\npreload_app!\n\non_worker_boot do\n  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)\nend<\/code><\/pre>\n<h2>5. Managing Puma with systemd<\/h2>\n<p>Create a dedicated systemd service unit to handle automatic recovery, zero-downtime reloads, and boot-time startup at <code>\/etc\/systemd\/system\/puma.service<\/code>:<\/p>\n<pre><code>[Unit]\nDescription=Puma HTTP Server for Rails Application\nAfter=network.target postgresql.service\n\n[Service]\nType=simple\nUser=deploy\nWorkingDirectory=\/var\/www\/rails_app\nExecStart=\/home\/deploy\/.rbenv\/shims\/bundle exec puma -C config\/puma.rb\nExecReload=\/bin\/kill -USR1 $MAINPID\nRestart=always\nRestartSec=5s\nEnvironment=RAILS_ENV=production\nEnvironment=RAILS_MASTER_KEY=your_production_master_key_here\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n<p>Reload systemd, enable the service, and verify runtime health:<\/p>\n<pre><code>sudo systemctl daemon-reload\nsudo systemctl enable --now puma\nsudo systemctl status puma<\/code><\/pre>\n<h2>6. Configuring Nginx Reverse Proxy with Brotli &amp; HTTP\/2<\/h2>\n<p>Configure Nginx to serve static files directly and forward dynamic requests to Puma&#8217;s Unix domain socket at <code>\/etc\/nginx\/sites-available\/rails_app.conf<\/code>:<\/p>\n<pre><code>upstream puma_rails {\n    server unix:\/\/\/var\/www\/rails_app\/tmp\/sockets\/puma.sock fail_timeout=0;\n}\n\nserver {\n    listen 80;\n    server_name rails.yourdomain.com;\n    return 301 https:\/\/$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name rails.yourdomain.com;\n\n    ssl_certificate \/etc\/letsencrypt\/live\/rails.yourdomain.com\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/rails.yourdomain.com\/privkey.pem;\n    ssl_protocols TLSv1.2 TLSv1.3;\n    ssl_ciphers HIGH:!aNULL:!MD5;\n\n    root \/var\/www\/rails_app\/public;\n    client_max_body_size 50M;\n\n    location \/ {\n        try_files $uri @rails;\n    }\n\n    location @rails {\n        proxy_pass http:\/\/puma_rails;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header Host $http_host;\n        proxy_redirect off;\n    }\n\n    location ~ ^\/(assets|packs)\/ {\n        gzip_static on;\n        expires max;\n        add_header Cache-Control public;\n    }\n}<\/code><\/pre>\n<h2>7. Production Diagnostics &amp; Troubleshooting Matrix<\/h2>\n<table style=\"width:100%;border-collapse: collapse;margin: 20px 0\">\n<thead>\n<tr style=\"background: #1e293b;color: #38bdf8\">\n<th style=\"padding: 12px;border: 1px solid #334155\">Symptom<\/th>\n<th style=\"padding: 12px;border: 1px solid #334155\">Probable Root Cause<\/th>\n<th style=\"padding: 12px;border: 1px solid #334155\">Remediation Command<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding: 10px;border: 1px solid #334155\">Nginx 502 Bad Gateway<\/td>\n<td style=\"padding: 10px;border: 1px solid #334155\">Puma socket permissions or process not running<\/td>\n<td style=\"padding: 10px;border: 1px solid #334155\"><code>sudo systemctl restart puma &amp;&amp; ls -l \/var\/www\/rails_app\/tmp\/sockets\/<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 10px;border: 1px solid #334155\">Missing Master Key Error<\/td>\n<td style=\"padding: 10px;border: 1px solid #334155\"><code>config\/master.key<\/code> missing or not passed in systemd<\/td>\n<td style=\"padding: 10px;border: 1px solid #334155\">Pass <code>RAILS_MASTER_KEY<\/code> in <code>puma.service<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 10px;border: 1px solid #334155\">PG::ConnectionBad: connection refused<\/td>\n<td style=\"padding: 10px;border: 1px solid #334155\">PostgreSQL service inactive or credentials mismatch<\/td>\n<td style=\"padding: 10px;border: 1px solid #334155\"><code>sudo systemctl restart postgresql &amp;&amp; pg_isready<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>8. Kamal Deployment vs Systemd: Choosing Your Rails Deployment Strategy<\/h2>\n<p>With Rails 8 embracing <strong>Kamal<\/strong> as the official zero-downtime containerized deployment tool, engineers have two production pathways on Linux cloud VPS:<\/p>\n<ul>\n<li><strong>Systemd + rbenv (Bare-Metal Speed):<\/strong> As configured in this tutorial, running directly on Ubuntu via systemd provides minimal memory overhead, zero container abstraction penalties, and instant access to system-level profiling tools (<code>perf<\/code>, <code>htop<\/code>). Ideal for single-server setups where every megabyte of RAM matters.<\/li>\n<li><strong>Kamal (Dockerized Multi-Server Fleet):<\/strong> Kamal deploys your application in Docker containers via SSH, orchestrating Traefik reverse proxying and automated rolling updates across multiple VPS instances. It simplifies horizontal scaling when your Rails app expands to separate web, worker, and database nodes.<\/li>\n<\/ul>\n<h2>9. Active Storage Image Optimization with libvips<\/h2>\n<p>Modern Rails applications handle extensive image processing for user uploads. Defaulting to ImageMagick introduces heavy memory spikes. Swap to <code>libvips<\/code> by adding <code>gem 'image_processing', '~&gt; 1.2'<\/code> and setting in <code>config\/environments\/production.rb<\/code>:<\/p>\n<pre><code>config.active_storage.variant_processor = :vips<\/code><\/pre>\n<p><code>libvips<\/code> processes high-resolution images up to 8x faster while consuming one-tenth the memory of legacy processing libraries.<\/p>\n<div style=\"background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);border: 1px solid #334155;border-radius: 12px;padding: 28px;margin: 36px 0;text-align: center\">\n<h3 style=\"color: #38bdf8;margin-top: 0;font-size: 22px\">Scale Rails Applications Seamlessly on CpanelFree VPS<\/h3>\n<p style=\"color: #cbd5e1;font-size: 16px;line-height: 1.6;max-width: 680px;margin: 12px auto 24px auto\">Deploy high-concurrency Ruby on Rails apps with guaranteed CPU cores, blistering NVMe storage, and isolated memory architecture at affordable prices.<\/p>\n<p>    <a href=\"https:\/\/cpanelfree.com\/\" style=\"background: #38bdf8;color: #0f172a;font-weight: 700;padding: 12px 28px;border-radius: 6px;text-decoration: none;display: inline-block;font-size: 15px\">Launch High-Performance Rails VPS &rarr;<\/a>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Deploying a modern Ruby on Rails 7 or Rails 8 web application in production requires a resilient multi-tier infrastructure. While managed platforms offer quick deployments, hosting Rails directly on an enterprise Linux cloud VPS delivers unmatched cost efficiency, compute predictability, and granular architectural control. In this definitive guide, we walk through setting up a complete &#8230; <a title=\"How to Deploy Ruby on Rails 7\/8 with Puma and Nginx on Ubuntu 24.04\" class=\"read-more\" href=\"https:\/\/cpanelfree.com\/blog\/how-to-deploy-ruby-on-rails-puma-nginx-ubuntu\/\" aria-label=\"Read more about How to Deploy Ruby on Rails 7\/8 with Puma and Nginx on Ubuntu 24.04\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":4532,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-4475","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-hosting-news"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4475","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=4475"}],"version-history":[{"count":1,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4475\/revisions"}],"predecessor-version":[{"id":4489,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4475\/revisions\/4489"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/4532"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=4475"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=4475"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=4475"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}