Last Updated: July 15, 2019
· sheharyar

Deploying Rails app using Nginx, Puma and Capistrano 3

This guide is an alternative to James Dullaghan's Deploying Rails app using Nginx, Unicorn, Postgres and Capistrano to Digital Ocean that I made for myself. This is the first time I'm working with Puma, so if I've missed something, or if there's something that can be done in a better way, please comment and let me know.

Start by creating a Droplet (Ubuntu 14.04 LTS x64) and ssh to root in terminal:

ssh root@server              # replace 'server' with your vps ip

Change your password:


Create a new user and set privileges

adduser username

Find user privileges section, and duplicate the root line changing it to your new user:

# User privilege specification
root     ALL=(ALL:ALL) ALL
username ALL=(ALL:ALL) ALL

Press (CTRL+O), then (CTRL+X) to save and quit. Now it's time to configure ssh:

nano /etc/ssh/sshd_config

For better security, it's recommended that you disable root and change the ssh port (anything between 1025..65536):

Port 22 # change this to whatever port you wish to use
Protocol 2
PermitRootLogin no

At the end of sshd_config, enter:

UseDNS no
AllowUsers username

Press (CTRL+O), then (CTRL+X) to save and quit. Reload ssh:

reload ssh

Don't close root yet! Open a new shell and ssh to vps with new username (remember the port, or you're locked out!)

ssh username@server -p 7171

If everything's working fine, you can close root. We need to install packages now; in your new user ssh session, enter:

sudo apt-get update
sudo apt-get install curl git-core nginx -y

Install rvm, ruby and rubygems:

curl -sSL | bash -s stable
source ~/.rvm/scripts/rvm
rvm requirements
rvm install 2.1.0
rvm use 2.1.0 --default
rvm rubygems current

(RVM installation might fail, and it may ask you to download its GPG Keys, so do that and retry)

Install rails and bundler:

gem install rails --no-ri --no-rdoc -V
gem install bundler --no-ri --no-rdoc -V

Shake hands with Github/Bitbucket and Generate a public/private key pair:

ssh -T
ssh -T
ssh-keygen -t rsa 

Add it as a deployment key for your repository (Instructions: Github & Bitbucket). Also try cloning on the server manually to make sure it's working fine.

Add your own ssh key to the VPS' authorized_keys. In your local terminal session, enter:

cat ~/.ssh/ | ssh -p 7171 username@server 'cat >> ~/.ssh/authorized_keys'

Now, in your project's Gemfile, add these and bundle:

group :development do
    gem 'capistrano',         require: false
    gem 'capistrano-rvm',     require: false
    gem 'capistrano-rails',   require: false
    gem 'capistrano-bundler', require: false
    gem 'capistrano3-puma',   require: false


cap install

A Capfile in your root and a deploy.rb file in config folder will be created for you. Replace the contents of your Capfile with:

# Load DSL and Setup Up Stages
require 'capistrano/setup'
require 'capistrano/deploy'

require 'capistrano/rails'
require 'capistrano/bundler'
require 'capistrano/rvm'
require 'capistrano/puma'

# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

Replace the contents of config/deploy.rb with this (modify parameters according to your app):

# Change these
server 'server', port: 7171, roles: [:web, :app, :db], primary: true

set :repo_url,        ''
set :application,     'appname'
set :user,            'username'
set :puma_threads,    [4, 16]
set :puma_workers,    0

# Don't change these unless you know what you're doing
set :pty,             true
set :use_sudo,        false
set :stage,           :production
set :deploy_via,      :remote_cache
set :deploy_to,       "/home/#{fetch(:user)}/apps/#{fetch(:application)}"
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state,      "#{shared_path}/tmp/pids/puma.state"
set :puma_pid,        "#{shared_path}/tmp/pids/"
set :puma_access_log, "#{release_path}/log/puma.error.log"
set :puma_error_log,  "#{release_path}/log/puma.access.log"
set :ssh_options,     { forward_agent: true, user: fetch(:user), keys: %w(~/.ssh/ }
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, false  # Change to true if using ActiveRecord

## Defaults:
# set :scm,           :git
# set :branch,        :master
# set :format,        :pretty
# set :log_level,     :debug
# set :keep_releases, 5

## Linked Files & Directories (Default None):
# set :linked_files, %w{config/database.yml}
# set :linked_dirs,  %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

namespace :puma do
  desc 'Create Directories for Puma Pids and Socket'
  task :make_dirs do
    on roles(:app) do
      execute "mkdir #{shared_path}/tmp/sockets -p"
      execute "mkdir #{shared_path}/tmp/pids -p"

  before :start, :make_dirs

namespace :deploy do
  desc "Make sure local git is in sync with remote."
  task :check_revision do
    on roles(:app) do
      unless `git rev-parse HEAD` == `git rev-parse origin/master`
        puts "WARNING: HEAD is not the same as origin/master"
        puts "Run `git push` to sync changes."

  desc 'Initial Deploy'
  task :initial do
    on roles(:app) do
      before 'deploy:restart', 'puma:start'
      invoke 'deploy'

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      invoke 'puma:restart'

  before :starting,     :check_revision
  after  :finishing,    :compile_assets
  after  :finishing,    :cleanup
  after  :finishing,    :restart

# ps aux | grep puma    # Get puma pid
# kill -s SIGUSR2 pid   # Restart puma
# kill -s SIGTERM pid   # Stop puma

Create config/nginx.conf in your project directory and add this to it (again replacing with your parameters):

upstream puma {
  server unix:///home/username/apps/appname/shared/tmp/sockets/appname-puma.sock;

server {
  listen 80 default_server deferred;
  # server_name;

  root /home/username/apps/appname/current/public;
  access_log /home/username/apps/appname/current/log/nginx.access.log;
  error_log /home/username/apps/appname/current/log/nginx.error.log info;

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

  try_files $uri/index.html $uri @puma;
  location @puma {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;

    proxy_pass http://puma;

  error_page 500 502 503 504 /500.html;
  client_max_body_size 10M;
  keepalive_timeout 10;

Commit changes, push and make the initial deploy:

git add -A
git commit -m "Set up Puma, Nginx & Capistrano"
git push origin master
cap production deploy:initial

If everything goes smoothly, back in your ssh session, symlink your nginx.conf and start nginx. You only need to do this once (but you might have to restart nginx whenever you update your conf file).

sudo rm /etc/nginx/sites-enabled/default
sudo ln -nfs /home/username/apps/appname/current/config/nginx.conf /etc/nginx/sites-enabled/appname
sudo service nginx start

Now onwards, whenever you want to push a new deploy:

git add -A
git commit -m "Deploy Message"
git push
cap production deploy

13 Responses
Add your response


This looks great, I'm going to try it on a new VPS instance. I followed Digital Ocean's guide, but never got Foreman working. Your guide doesn't use it, and I think this would be easier to host more than one site on a single VPS. I'm loving Puma.

over 1 year ago ·

production log is blank and getting something wen wrong. How to debug?

over 1 year ago ·

how do you handle server reboots.?
right now i have to go in each rails project and do 'cap production puma:start'. great tutorial btw :)


found solution. puma comes with upstart configs (ubuntu)

i needed to add this to your script in the deploy-task

desc 'Set config/puma.rb-symlink for upstart'
task :pumaconfigln do
on roles(:app) do
execute "ln -s #{sharedpath}/puma.rb #{fetch(:deployto)}/current/config/puma.rb"

after :finishing, :pumaconfigln

over 1 year ago ·

@jbmyid Yeah, has happened to me once. I was able to fix it using this:

You might want to change the unicorn commands with the puma ones (cap production puma:restart).

over 1 year ago ·

For rvm, the installation failed for me. What did work was:

curl -sSL | bash -s stable --rails


over 1 year ago ·

I have one problem, because I follow step to step this tutorial... but, when I run "cap production deploy:initial" show me the next error:

Net::SSH::AuthenticationFailed: Authentication failed for user sbrocos@servername

(and I restinstal netssh with 2.7.0 version)


over 1 year ago ·

@benmorganio Guide updated, thanks.

over 1 year ago ·

I followed this setup but Im getting this error: 403 Forbidden
I have already check /home/user permission (it's ok: 755).

over 1 year ago ·

@snysantos Can you explain a bit more about your set up? I can't help you with only this information.

I wrote this guide for myself, and I use it to set-up every production rails app on a VPS, and the last time I used this guide was 4 days ago, so believe me when I say, it works.

over 1 year ago ·

All the stuff works, but at the point of the deploy, puma and capistrano doesnt start. (Doesnt do the "rails server" command) but if I do the rails s on the server it works. Problem is.. doesnt do rails s automatically. Can u help ?

over 1 year ago ·

@Vor7exx Must be something different in your configuration because Puma is configured to automatically start after the first deploy and each other deploy simply restarts your puma instance.

You can run this command (on your local machine) to start puma on the server:

cap production puma:start
over 1 year ago ·

My deploy is equal this tutorial but i have problem with environment variable, check:

over 1 year ago ·

Thanks :) will try this one.

over 1 year ago ·