DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk15 min

How to Set Up Ruby on Rails With PostgreSQL on an Ubuntu VPS

A complete Ubuntu VPS runbook for Ruby on Rails and PostgreSQL, covering Ruby version pinning, least-privilege database setup, Rails production configuration, Kamal or Puma deployments, Nginx, systemd, HTTPS, and launch checks.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You can run a production Ruby on Rails application with PostgreSQL on a VPS in two dependable ways: deploy the app as a Docker container with Kamal, or run Ruby, Puma, PostgreSQL, Nginx, and systemd directly on Ubuntu. Both work, but they differ in who owns the server configuration, how deployments roll back, and how much memory and automation you need.

This guide takes you from a new Ubuntu LTS VPS to a verified HTTPS deployment. It covers SSH hardening, a pinned Ruby runtime, a least-privilege PostgreSQL database, Rails production configuration, secrets, Puma supervision, Nginx proxying, TLS, migrations, and the checks that catch common deployment failures.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose your deployment model first

Your architecture determines nearly every later command. Kamal is the container-oriented path documented in the current Rails Getting Started guide. The traditional path follows the common Ubuntu layout described by Scaleway in its tutorial reviewed July 28, 2025: rbenv for Ruby, PostgreSQL on the host, Puma as the Rails server, Nginx as the public reverse proxy, Capistrano for releases, and systemd for supervision.

Decision Kamal Traditional VPS stack
Where Rails runs Inside a Docker container built from the application image As a host process launched by Puma
Deployment command bin/kamal setup, then bin/kamal deploy Usually Capistrano tasks followed by a systemd restart or reload
Downtime and rollback Kamal is designed for zero-downtime container replacement; rollback is an image or release change Downtime depends on the release procedure and service restart; rollback means selecting a previous release and restarting Puma
Configuration ownership Application image, proxy settings, registry credentials, and the remote Docker host Ruby installation, gems, filesystem permissions, systemd units, Nginx, logs, and every host package
Resource overhead Docker and image layers add some overhead and require registry access Fewer container layers, but more host-level maintenance
Best fit A team that wants repeatable container releases and less drift between servers An operator who wants direct control of Ubuntu services or is integrating with an existing Capistrano workflow

The Rails guide says Kamal requires an Ubuntu LTS server with at least 1 GB of RAM, a Docker Hub account and token, and DNS pointing at the server before its proxy can obtain a Let’s Encrypt certificate. The traditional route has no Docker registry requirement, but you must maintain the service stack yourself. Pick one route before creating deployment scripts; mixing pieces from both usually produces unclear ownership and unreliable restarts.

What you need before connecting to the VPS

  • An Ubuntu LTS image. The traditional Scaleway procedure uses Ubuntu 22.04; use the LTS release supported by your application and deployment tooling.
  • An SSH key loaded on the VPS. Disable password login after confirming key-based access.
  • A domain name whose DNS can be changed to the VPS address.
  • The application’s required Ruby version, recorded in .ruby-version or an equivalent version file.
  • The Rails production credentials: a master key, database connection information, and any provider API keys required by the application.
  • A decision about deployment model: Kamal with Docker, or host Ruby with Puma, Nginx, systemd, and optionally Capistrano.

A 1 GB server is the minimum stated for the Kamal path, not a performance guarantee. Rails, PostgreSQL, Docker image builds, asset compilation, and background jobs can need more memory. If the application swaps heavily during bundle installation or asset compilation, increase the VPS size rather than treating swap activity as normal capacity.

1. Provision and harden the Ubuntu server

Log in with the provider’s initial administrative account, update the system, create a non-administrative deployment account, and limit inbound traffic to what the application needs.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sudo apt update
sudo apt -y upgrade
sudo adduser deploy
sudo usermod -aG sudo deploy
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Copy the authorized SSH key to /home/deploy/.ssh/authorized_keys, test a second terminal with ssh deploy@server-address, and only then harden the SSH daemon. Keep an existing administrative session open while testing so a typo cannot lock you out. A typical policy is to prohibit root login and password authentication after key access has been verified:

sudoedit /etc/ssh/sshd_config
# Set these values:
PermitRootLogin no
PasswordAuthentication no
sudo systemctl reload ssh

Do not expose PostgreSQL’s port 5432 to the public internet for a single-server Rails installation. Rails and PostgreSQL can communicate over the loopback interface. If a separate application host or managed database is introduced later, restrict access with a private network or an allowlist rather than opening the port globally.

2. Install and pin Ruby with rbenv

Use the Ruby version required by the application. The distribution’s default Ruby can lag behind the version declared by Rails or by a gem, so install rbenv and select the exact version deliberately.

sudo apt update
sudo apt install -y git curl build-essential autoconf bison 
  rustc libssl-dev libyaml-dev libreadline-dev zlib1g-dev 
  libncurses5-dev libffi-dev libgdbm-dev libdb-dev

sudo -iu deploy
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init - bash)"
git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
rbenv install -l
rbenv install RUBY_VERSION
rbenv global RUBY_VERSION
ruby --version
 gem install bundler
rbenv rehash

In the example, replace RUBY_VERSION with the version declared by the application, such as the value in .ruby-version. Do not silently choose a different patch release if the project locks one. Commit the version file and the lockfile so every deployment uses the same runtime and dependency resolution rules.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a Capistrano deployment, the deploy user should own the application directory and the shared directories used for secrets, logs, and uploaded files. For Kamal, Ruby runs inside the image; the host still needs Docker, SSH access, and enough disk space for image layers.

3. Install PostgreSQL and verify the service

Install PostgreSQL, its contributed extensions, and the client headers needed to compile the pg Ruby gem.

sudo apt update
sudo apt install -y postgresql postgresql-contrib libpq-dev
sudo systemctl enable --now postgresql
sudo systemctl status postgresql --no-pager

Create a role dedicated to this application and a database owned by that role. Avoid using the PostgreSQL superuser from Rails. The safest interactive approach is to create the role without putting its password in shell history:

sudo -u postgres psql
CREATE ROLE myapp LOGIN;
password myapp
CREATE DATABASE myapp_production OWNER myapp;
q

Use a strong, unique password and store it in the deployment secret store or environment, not in Git. Test the same credentials Rails will use before deploying the application:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
psql --host=127.0.0.1 --username=myapp --dbname=myapp_production --password -c 'SELECT current_user, current_database();'

The command should return myapp and myapp_production. If it fails, fix PostgreSQL authentication, the role password, or the database name now; a Rails error later is only a less precise version of the same problem.

4. Configure Rails to use PostgreSQL

Use an explicit production database configuration

Rails’ PostgreSQL adapter uses adapter: postgresql, Unicode encoding, a database name, and a connection pool. A production section in config/database.yml can read credentials from environment variables:

production:
  adapter: postgresql
  encoding: unicode
  database: myapp_production
  username: myapp
  password: <%= ENV.fetch('DATABASE_PASSWORD') %>
  host: 127.0.0.1
  port: 5432
  pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>

Alternatively, provide a complete DATABASE_URL, for example postgresql://myapp:[email protected]:5432/myapp_production, through the deployment environment. When DATABASE_URL and config/database.yml are both present, Rails merges the settings. Document which source is authoritative for each value; otherwise an old hostname or pool size in one file can unexpectedly override the value you intended to change.

Understand pool size before increasing it

The pool is the number of database connections Rails can check out for a process. Set it high enough for the Puma thread count and any internal concurrency, but remember that every Puma worker and every separate job process can create its own pool. A pool of five in four processes can require up to twenty PostgreSQL connections. Check PostgreSQL’s connection limit before scaling workers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Account for Rails PostgreSQL defaults

  • Rails enables advisory locks by default. If the application connects through an external pooler such as PgBouncer, Rails documents considering advisory_locks: false, because transaction pooling can make session-level lock behavior unsuitable.
  • Rails allows up to 1,000 prepared statements per database connection by default. Prepared statements can reduce parsing work, but they consume PostgreSQL memory. Lower statement_limit or disable prepared statements when the database is memory-constrained and monitoring shows statement growth.
  • Rails retries only safely retryable database queries by default. The documented default is one retry, configurable with connection_retries. A retry is not a substitute for fixing exhausted connections, failed storage, or a database that is restarting.

5. Set production mode and protect secrets

Rails selects its environment through RAILS_ENV. Set it to production for every release, migration, and service process that is intended to serve the public application.

export RAILS_ENV=production
export RAILS_MASTER_KEY=your-real-master-key
export DATABASE_URL=postgresql://myapp:[email protected]:5432/myapp_production

The values above illustrate the variables; do not commit real credentials or place them in a repository, Docker image layer, shell history, or a world-readable file. Use the VPS deployment secret mechanism, a protected systemd environment file, or Kamal’s secrets configuration with permissions restricted to the deployment account and service.

Rails encrypted credentials require the matching master key at boot. A missing or incorrect key commonly produces an error before Puma binds to its port, so test the key with a production boot command before switching traffic.

6. Install gems, prepare the database, and compile assets

From the application release directory, install the locked dependencies in deployment mode and run the tasks your project requires.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
bundle config set --local deployment true
bundle config set --local without development test
bundle install
RAILS_ENV=production bin/rails db:prepare
RAILS_ENV=production bin/rails assets:precompile

db:prepare creates the database when needed and runs pending migrations. Some teams use a separate migration task in their release pipeline; follow the project’s established convention and make sure only one release process runs migrations at a time. Asset compilation is required for applications using the asset pipeline; applications using a different build system should run that system’s production build instead.

Run a local production boot check before involving Nginx:

RAILS_ENV=production bundle exec puma -C config/puma.rb

In another terminal, request the Puma listener with curl. Stop the foreground process after confirming the app responds. If boot fails, inspect the first exception rather than repeatedly restarting the service; missing gems, credentials, database authentication, and asset manifests are the usual categories to resolve.

7. Deploy with Kamal

Kamal builds and runs the Rails application as a Docker container on the VPS. The Rails Getting Started guide describes it as using Docker containers with zero-downtime deployment. You need Docker on the server, a Docker registry account and token, SSH access, and DNS that already resolves to the server before enabling the proxy’s Let’s Encrypt configuration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Configure the application image, registry credentials, server address, service name, and environment secrets in the project’s Kamal configuration.
  2. Install Docker on the Ubuntu VPS and confirm the deploy user can invoke it according to your chosen Docker permissions model.
  3. Point the domain’s DNS record at the VPS and wait for the record to resolve publicly.
  4. Run bin/kamal setup. This prepares the remote host, pulls the required image or builds the initial release, and starts the proxy and application services.
  5. Run bin/kamal deploy for subsequent releases. Watch the command output for image build, health-check, migration, and proxy status failures.

Enable SSL in the Kamal proxy settings only after the DNS record points to the server and ports 80 and 443 are reachable. Kamal can then ask Let’s Encrypt to issue the certificate for the domain. Certificate issuance will fail if DNS still points elsewhere, the domain is not publicly resolvable, or the firewall blocks the HTTP challenge.

Kamal reduces configuration drift, but it does not remove operational responsibility. Keep registry tokens private, monitor disk usage from old images, define a database backup plan, and verify that the application can reach PostgreSQL from the container’s network. A database on the host may require an address other than the container’s loopback interface; do not assume 127.0.0.1 inside a container means the VPS host.

8. Deploy the traditional Puma, systemd, and Nginx stack

Organize releases and shared files

A common layout is /home/deploy/apps/myapp/releases for timestamped releases, current as a symlink to the active release, and shared for logs, uploaded files, and environment files. Capistrano can create and switch these directories atomically. Ensure the deploy user owns files Puma must read and write, while Nginx only needs to proxy to Puma and read any files it serves directly.

Configure Puma

A minimal production config/puma.rb should bind to a local socket or loopback port, set the environment, and write its PID and state files somewhere the service user can access:

environment ENV.fetch('RAILS_ENV', 'production')
threads ENV.fetch('RAILS_MAX_THREADS', 5), ENV.fetch('RAILS_MAX_THREADS', 5)
bind 'unix:///home/deploy/apps/myapp/shared/tmp/sockets/puma.sock'
pidfile '/home/deploy/apps/myapp/shared/tmp/pids/puma.pid'
state_path '/home/deploy/apps/myapp/shared/tmp/pids/puma.state'
stdout_redirect '/home/deploy/apps/myapp/shared/log/puma.stdout.log', '/home/deploy/apps/myapp/shared/log/puma.stderr.log', true
preload_app!

Create the socket, PID, and log directories before starting the service. Match Puma’s thread count to the Rails database pool and account for every worker process if you use clustered mode.

Supervise Puma with systemd

Save a unit such as /etc/systemd/system/myapp-puma.service, adjusting paths and the Ruby environment for your installation:

[Unit]
Description=Myapp Rails Puma
After=network.target postgresql.service

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/apps/myapp/current
Environment=RAILS_ENV=production
EnvironmentFile=/home/deploy/apps/myapp/shared/.env
ExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C /home/deploy/apps/myapp/current/config/puma.rb
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Load and start it with:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp-puma
sudo systemctl status myapp-puma --no-pager
sudo journalctl -u myapp-puma -n 100 --no-pager

A healthy systemd unit should remain active after a shell logout and restart after a process crash. If it enters a restart loop, run the same ExecStart command as the deploy user with RAILS_ENV=production to expose the underlying boot error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Put Nginx in front of Puma

Nginx accepts public HTTP traffic and proxies it to Puma’s Unix socket:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://unix:/home/deploy/apps/myapp/shared/tmp/sockets/puma.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
}

Enable the site, check the configuration, and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx --no-pager

A 502 response usually means Nginx cannot reach Puma: the socket path differs, the socket directory permissions prevent access, or the Puma service is stopped. Check the Nginx error log and journalctl -u myapp-puma together so you can distinguish a proxy problem from a Rails boot failure.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

9. Point DNS and enable HTTPS

Create an A record for the application hostname pointing to the VPS IPv4 address, and an AAAA record only if the server is correctly configured for IPv6. Verify resolution from outside the VPS before requesting a certificate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
dig +short example.com
curl -I http://example.com

For Kamal, enable its proxy SSL setting after DNS is correct; the proxy requests a Let’s Encrypt certificate. For the traditional stack, use your chosen ACME-compatible certificate procedure and configure Nginx to listen on 443, redirect HTTP to HTTPS, and pass the original protocol through with X-Forwarded-Proto. Renewals must be tested before the first certificate expires.

Do not terminate TLS at Nginx and then configure Rails as though every request were plain HTTP. Rails URL generation, secure cookies, and redirect behavior depend on correct proxy headers and trusted proxy configuration.

10. Verify the deployment before launch

Run these checks from the VPS and from an external machine. They cover the failure points that a successful package installation does not prove.

  1. Confirm services: systemctl status postgresql, systemctl status nginx, and either systemctl status myapp-puma or the Kamal service status.
  2. Confirm database credentials with psql using the same host, database, role, and password supplied to Rails.
  3. Confirm migrations: run RAILS_ENV=production bin/rails db:prepare or inspect the release task output and verify the expected schema version.
  4. Confirm Rails logs show a successful boot without missing credentials, missing assets, or connection errors.
  5. Request Puma directly over its local socket or port and verify a valid Rails response.
  6. Request the public HTTP endpoint through Nginx, then request the HTTPS endpoint and inspect the certificate and redirect.
  7. Restart each supervised service deliberately and confirm it returns without manual intervention.
  8. Reboot the VPS during a maintenance window and verify that PostgreSQL, Puma or Kamal, and Nginx return in the correct order.

Common failures and their fixes

Rails cannot connect to PostgreSQL

Check that PostgreSQL is active, the database role exists, the password is current, and the host matches the network layout. A host process can use 127.0.0.1; a container may need the host gateway or a private network address. Test with psql first, then compare the effective Rails environment.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Bundler reports an incompatible Ruby or native extension

Read the project’s version file and lockfile, install that Ruby with rbenv, and run ruby --version as the same user that systemd or Capistrano uses. Install libpq-dev before rebuilding the pg gem.

Puma starts manually but not under systemd

Systemd does not load your interactive shell profile in the same way. Use the absolute rbenv shim path, set WorkingDirectory, define RAILS_ENV, provide the environment file, and ensure the service user can read the release and socket directories. The journal normally contains the first missing path or variable.

Nginx returns 502

Verify that Puma is active, the socket exists, and Nginx can traverse every parent directory. Confirm the socket path in puma.rb exactly matches proxy_pass. If Puma is listening on a TCP port instead, proxy to that port rather than a nonexistent socket.

Let’s Encrypt cannot issue a certificate

Check public DNS, confirm ports 80 and 443 reach this VPS, remove conflicting proxy records while issuing the certificate, and verify the hostname in the proxy configuration. Kamal’s certificate request cannot succeed while the domain resolves to another server.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Deployments exhaust database connections

Count connections from every Puma worker, thread pool, console, and background process. Reduce pool or worker counts, increase PostgreSQL’s permitted connections only when the VPS has memory for them, and account for the memory used by prepared statements. If an external pooler is present, review advisory-lock and prepared-statement settings rather than copying a single-server configuration unchanged.

Operational practices after the first release

  • Keep Ruby, Rails, PostgreSQL, the operating system, and container images patched on a planned schedule.
  • Back up PostgreSQL and test restoring a backup; a deployment is not a recovery plan.
  • Monitor disk space, memory, swap, PostgreSQL connections, Puma restarts, Nginx errors, and certificate expiry.
  • Make migrations backward-compatible when deploying a new application version so old and new processes can overlap during a rolling or zero-downtime release.
  • Keep release logs and record which commit, Ruby version, database migration, and image tag is active.
  • Use a staging VPS or equivalent pre-production environment to test migrations, asset builds, and certificate configuration before changing the production domain.

Final launch checklist

  • Ubuntu LTS is updated, SSH keys work, root/password SSH access is restricted, and only required firewall ports are open.
  • The application’s Ruby version is pinned and the deployment user runs the same Ruby and Bundler versions used during installation.
  • PostgreSQL is enabled, the application role is not a superuser, and a direct psql login succeeds.
  • database.yml and DATABASE_URL have an intentionally documented relationship, with a pool sized for all application processes.
  • Production secrets and the Rails master key are outside Git and readable only by the deployment process.
  • Database preparation, migrations, and asset compilation complete successfully in production mode.
  • Kamal or Puma is supervised, Nginx proxies correctly, and service status remains healthy after a restart.
  • DNS resolves to the VPS, HTTPS is valid, HTTP redirects as intended, and certificate renewal has an owner.
  • Logs, backups, monitoring, and a rollback procedure are written down before public traffic is enabled.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.