iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
The safest way to provision a Rails VPS with Ansible is to model the server as a small, versioned system: Nginx accepts public traffic, Puma runs the Rails application, PostgreSQL stores data, systemd supervises processes, and Ansible roles install and configure each layer. Keep first-boot provisioning separate from application releases, store secrets outside Git, and make every task idempotent so a failed run can be corrected and rerun without damaging the host.
This guide uses the conventional single-VPS architecture and an Ubuntu 22.04 (Jammy) example. Adapt package names and service details if you choose another supported Ubuntu LTS or a different database and process manager.
Free tools Windows power users keep installed
One-click scans. No signup required.
What the finished server looks like
A typical single-VPS Rails deployment has this request path:
Internet → Nginx → Puma → Rails → PostgreSQL
#1 Best Overall
Nginx terminates TLS, serves compiled static files when Rails is configured to do so, and proxies dynamic requests to Puma over a local socket or loopback port. Puma runs one or more Rails workers as an unprivileged application user. PostgreSQL can run on the same VPS or on a private managed service. systemd starts Puma (and any worker process), restarts failed services, and records service state in the journal.
rbenv is a practical way to install the Ruby version required by the application’s Gemfile. Capistrano-style releases can deploy known revisions into timestamped directories, while Ansible remains responsible for the host, service units, users, packages and baseline configuration.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Prerequisites and decisions to make first
Host prerequisites
- A supported Ubuntu LTS VPS; the representative setup here assumes Ubuntu 22.04 Jammy.
- An SSH key that can log in to the instance.
- Initial root or sudo-capable access for bootstrap tasks.
- A DNS name pointed at the VPS if you intend to use HTTPS.
- A local workstation or CI runner with a pinned Ansible version and access to the repository.
Pin the inputs
Do not let a playbook silently select “latest” versions. Put the operating-system assumptions, Ruby version, Rails dependency set, PostgreSQL major version, Nginx version, Puma version and Ansible version in documentation or variables. The application’s .ruby-version, Gemfile.lock and JavaScript lockfile should be treated as release inputs. Update those pins deliberately and test the resulting release before changing production.
Choose where PostgreSQL runs
| Choice | Advantages | Costs and responsibilities |
|---|---|---|
| Same VPS | Lowest network latency, one private machine to manage, and no separate database bill. | CPU, memory, disk and failure domain are shared with Rails. You own backups, restore testing, upgrades, replication decisions and capacity planning. |
| Managed database | Usually provides an independent failure domain, managed backups and easier high availability. | Additional recurring cost, network latency and provider-specific configuration. You still need credentials, access controls and restore verification. |
For a small installation, a local database is straightforward. Move PostgreSQL to a managed service when isolation, failover or operational capacity matters more than the simplicity of one host.
Organize the Ansible repository
Use roles with narrow responsibilities. A layout like this keeps the playbook readable and allows a rebuild or a later multi-host split:
ansible/
inventory/production/hosts.yml
group_vars/production.yml
host_vars/rails-vps.yml
playbooks/site.yml
roles/
common/
ruby/
postgresql/
rails_app/
puma/
nginx/
deploy/
Ansible describes playbooks as repeatable and reusable configuration-management and multi-machine deployment artifacts. Keep environment-specific values in group or host variables, and encrypt sensitive values with Ansible Vault or inject them from an external secret system at runtime.
Example inventory
all:
children:
rails:
hosts:
rails-vps:
ansible_host: 203.0.113.10
ansible_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/rails-vps
Use your real address and key path in the local file or CI secret store. Never commit a private key.
Rank #2
Example variables
app_name: myapp
app_user: deploy
app_group: deploy
app_root: /srv/myapp
ruby_version: "3.2.2"
app_revision: "v2026.10.1"
postgresql_database: myapp_production
postgresql_user: myapp
puma_bind: unix:///run/myapp/puma.sock
rails_env: production
Database passwords, RAILS_MASTER_KEY, cloud tokens and SMTP credentials do not belong in this plaintext file. Reference a vaulted variable or a secret injected by your deployment system instead.
Build the site playbook
Compose roles in dependency order. A minimal site playbook can look like this:
- name: Provision Rails hosts
hosts: rails
become: true
roles:
- common
- ruby
- postgresql
- rails_app
- puma
- nginx
If workers, monitoring or backups are separate concerns, add roles for them rather than putting unrelated shell commands in the site playbook. Ansible can also compose common, database, web and monitoring roles in a rolling-upgrade workflow and be invoked from CI/CD or AWX.
Recommended Free Tools
Step 1: Bootstrap a non-root deployment user
The first run may use the image’s default administrator account. Create a dedicated user for releases and services, install its authorized key, and grant only the sudo permissions that provisioning actually needs.
- Create the user and its primary group with
ansible.builtin.user. - Install the public SSH key with
ansible.posix.authorized_key. - Create a narrowly scoped sudoers entry with
ansible.builtin.copyortemplate; validate it before enabling it. - After confirming a second login works, disable password authentication and direct root login where your recovery process permits.
Keep the deploy user separate from root. Rails, Puma and workers should run as this unprivileged account; root should be reserved for tasks that require system changes.
Step 2: Apply common host configuration
Packages and build dependencies
Use ansible.builtin.apt with an explicit package list. Include compiler and library packages required by native gems, PostgreSQL client headers, Git, a JavaScript runtime and any image-processing libraries your application actually uses. Updating the package cache can be a separate task with a controlled cache-validity period.
Time, firewall and log policy
- Configure a time-synchronization service and verify the timezone expected by your operations team.
- Allow only SSH, HTTP and HTTPS on the public interface. Permit PostgreSQL only on loopback or a private network when it is not local-only.
- Configure log rotation for Nginx, Rails and worker logs. Journal retention must leave enough history to diagnose a failed release without exhausting disk.
- Install a disk-usage check and alert before the filesystem reaches the point where PostgreSQL or Puma cannot write.
Represent firewall rules, sysctl settings and log policies as templates or module parameters so a second run converges on the same state.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Step 3: Install Ruby and application tooling
The Ruby role should install rbenv for the deploy user, the exact Ruby version required by the application, the matching Bundler version and the JavaScript tooling needed to compile assets. Record all of these as variables.
- Install the build libraries and Git as the common role.
- Install rbenv and its Ruby-build component for the deploy user.
- Compile or install
ruby_version, then set it as the user’s global or application-local version. - Install the Bundler version declared by the lockfile or deployment policy.
- Install Node.js and the package manager version required by the application’s lockfile.
Run these operations with the deploy user where possible. If a command is unavoidable, guard it with creates, set an accurate changed_when, or provide a check-mode implementation. An unguarded “install Ruby” shell command is not idempotent and will make every playbook run look like a change.
Step 4: Install and secure PostgreSQL
The PostgreSQL role should install the selected major version, create the application role and database, and apply access rules that match your topology.
- Install the server and client packages.
- Enable and start the database service.
- Create a login role owned by the application, with a password supplied from a secret variable.
- Create the production database with the intended owner and encoding.
- Restrict
pg_hba.confto local sockets or the private application network; never expose broad public access for convenience. - Reload PostgreSQL after authentication configuration changes and verify a real application connection.
Take a verified backup before migrations that drop or transform data. A backup job that has never been restored is an assumption, not a recovery plan. Schedule restore drills and document the time and steps required to recover onto a clean host.
Step 5: Create the application release layout
Give the deploy user ownership of a stable root such as /srv/myapp. A release layout separates immutable code from shared configuration and persistent data:
/srv/myapp/
releases/20261015120000/
current -> releases/20261015120000
shared/
config/master.key
config/database.yml
log/
tmp/pids/
tmp/sockets/
storage/
Use ansible.builtin.file to create directories with explicit owners and modes. Check out a requested Git revision with ansible.builtin.git; do not deploy an unpinned branch when you need reproducibility.
Install gems with deployment settings, load the production environment, and compile assets in the release directory. Shared secrets and persistent uploads should be symlinked into the release, never copied into every revision. Ensure the master key and database configuration are readable only by the deploy user and the services that need them.
Rank #4
Step 6: Run migrations as an explicit release action
Migrations can be irreversible and should not be hidden inside a generic “restart application” task. Make the migration command a named deployment step with a backup and rollback decision before it runs.
- Confirm the release revision and database backup status.
- Install the release’s dependencies and compile assets.
- Run
RAILS_ENV=production bin/rails db:migrateas the deploy user. - Check migration output and application health before switching the
currentsymlink. - If the migration is destructive, use an expand-and-contract pattern or a tested restore procedure rather than assuming the previous code can immediately run against the changed schema.
Step 7: Run Puma under systemd
Render a dedicated unit with Ansible’s ansible.builtin.systemd_service module and a template. The fully qualified module name avoids collection-name conflicts; systemd is a backward-compatible alias. If you must support hosts using different init systems, ansible.builtin.service is the portability layer that proxies to the detected service manager.
[Unit]
Description=MyApp Puma
After=network.target postgresql.service
[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/srv/myapp/current
Environment=RAILS_ENV=production
EnvironmentFile=/srv/myapp/shared/config/myapp.env
ExecStart=/home/deploy/.rbenv/bin/rbenv exec bundle exec puma -C config/puma.rb
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Use the paths and command form appropriate to your rbenv installation. Store the environment file outside the release directory, restrict its mode, and avoid putting secrets directly in the unit template.
Enable and start the unit only after its directories, socket path and release exist. A handler should run daemon reload and restart Puma when the unit or Puma configuration changes. Ordinary playbook runs should not restart a healthy service unnecessarily.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesStep 8: Configure Nginx as the reverse proxy
Render a server block that listens on the required names, serves static assets according to your Rails configuration, and proxies dynamic requests to Puma’s Unix socket or loopback port. Set the forwarding headers Rails needs, configure request and proxy timeouts for your workload, and reject unknown hostnames where appropriate.
server {
listen 80;
server_name example.com;
root /srv/myapp/current/public;
location /assets/ {
try_files $uri =404;
access_log off;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://unix:/run/myapp/puma.sock;
}
}
Validate the rendered file with nginx -t before reloading. Attach a handler so a syntax-validated configuration change reloads Nginx, while an unchanged file leaves the running process alone.
Terminate HTTPS at Nginx
Use an automated certificate-renewal process, redirect HTTP to HTTPS after the certificate is active, and test renewal before depending on it. Rails must be told that the proxy is trusted and that production requests are HTTPS; otherwise secure cookies, redirects and URL generation can be wrong.
Best Value
Handlers and idempotency rules
Handlers are the connection between configuration and service lifecycle. A changed Puma unit should notify a daemon-reload and restart handler; a changed Nginx template should notify a configuration-test-and-reload handler. Do not restart services at the end of every play regardless of whether anything changed.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11- Prefer package, user, file, template, Git and service modules to shell.
- Use
creates,removes,changed_whenandfailed_whenwhen a command has no suitable module. - Run
ansible-playbook --check --diffbefore a production change and review rendered secrets carefully. - Make tasks safe to run after a partial failure; a rerun should finish the missing work rather than duplicate users, databases or releases.
Provisioning versus application releases
Provisioning establishes the machine. A release changes application code. Keeping them separate makes a replacement VPS predictable and makes application rollback smaller.
| Provisioning workflow | Release workflow |
|---|---|
| OS packages, users, SSH policy, firewall, Ruby, PostgreSQL, directories, service units, Nginx and log policy. | Fetch a known revision, install gems and JavaScript dependencies, compile assets, migrate, switch the release, restart or reload Puma, and run health checks. |
| Run infrequently and review as infrastructure change. | Run for each tested application revision through CI/CD or an approved operator. |
| Designed to rebuild a host from an empty image. | Designed to deploy, verify and roll back a release without rebuilding the host. |
Capistrano-style releases and container workflows
| Approach | Strengths | Trade-offs |
|---|---|---|
| Capistrano-style releases | Natural fit for a single VPS, timestamped revisions, shared directories and simple symlink rollback. | Host Ruby and system libraries remain part of the maintenance surface. |
| Container/Kamal-style workflow | Packages application dependencies into an image, improving image reproducibility and making the release artifact explicit. | Requires an image registry, container runtime and more host orchestration; persistent storage and database migration sequencing still need care. |
Either approach can use Ansible for base-host provisioning. Choose the release mechanism your team can monitor and roll back reliably.
Verification after a provisioning run
- Run
ansible-playbook --check --diffand confirm the second normal run reports no unexpected changes. - Check service state with
systemctl status puma,systemctl status nginxandsystemctl status postgresql. - Inspect recent logs with
journalctl -u puma -n 100 --no-pagerand the corresponding Nginx and PostgreSQL logs. - Call a local Rails health endpoint through Nginx and verify the expected status code and response body.
- Test database connectivity using the same user and environment that Puma uses.
- Request the public HTTPS URL, confirm the certificate chain, redirect behavior, asset loading and a session-backed page.
- Exercise a controlled Puma restart and confirm systemd brings it back.
- Check disk space, backup freshness, certificate-renewal status and monitoring alerts.
Troubleshooting common failures
Puma will not start
Check the unit’s WorkingDirectory, rbenv path, Bundler version, environment file permissions and socket directory. Run the Puma command manually as the deploy user from the release directory, then read the full journal rather than only the final systemd status line.
Nginx returns 502 Bad Gateway
Confirm Puma is active and that the socket path in Nginx exactly matches Puma’s bind path. Verify the Nginx worker can traverse the parent directories and access the socket. A stale socket after a crash may require a controlled service restart.
Assets are missing
Confirm the release compiled assets in production mode, that Nginx’s root points at current/public, and that the Rails asset host and static-file settings match the proxy design. Inspect the browser’s requested asset path and Nginx error log.
Database authentication fails
Check the effective environment file, PostgreSQL role ownership, database name, socket versus TCP settings, and the relevant pg_hba.conf rule. Reload PostgreSQL after changing access rules and test with the same credentials outside Rails.
Ansible reports changes every run
Find commands that always return success while changing state, templates that contain unstable timestamps, and package tasks that request unpinned latest versions. Replace shell with a module or add a precise idempotency guard.
Security and reliability checklist
- Use SSH keys and disable password login when you have verified console or recovery access.
- Allow only required public ports; keep database traffic private.
- Keep
RAILS_MASTER_KEY, database credentials and provider tokens outside source control, with restrictive ownership and permissions. - Run Rails, Puma and workers as an unprivileged user.
- Terminate TLS at Nginx, automate renewal and test renewal.
- Rotate logs and monitor disk, Puma, workers, PostgreSQL, backups and certificate expiry.
- Take and verify backups before destructive migrations; perform restore drills.
- Record the OS, Ruby, Rails, PostgreSQL, Nginx, Puma and Ansible versions for each release.
- Keep a tested rollback path for both code and database changes.
Final operating model
A dependable Rails VPS is not produced by a single long shell script. It is the result of explicit inputs, role boundaries, least-privilege users, handlers tied to real changes, and two separate workflows: Ansible provisions the host, while a release process deploys and verifies a known application revision. With that model, rebuilding the VPS and repeating a deployment become routine operations rather than emergency improvisation.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
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.

