Rails Server with Apache + Puma (via reverse proxy)

Passenger vs Puma vs Unicorn

As for 2019, there are 3 main options to serve a Rails application:

  • Phusion Passenger
  • Puma
  • Unicorn

Phusion Passenger is an Apache/Nginx module. It works just like mod_php (it is sometimes referred to mod_rails). Passenger is probably the most used solution for production.

Puma and Unicorn, are stand-alone web servers. If you call them inside a rails project root folder, they will listen to the port 80 (or any other of your choice), wait for web connections, run the rails code and answer to the browser – no need for Apache, Nginx, or anything else.

This tutorial shows how to run Puma with Apache. The only way to do this is through a reverse proxy, as there are no FastCGI or Apache/Nginx modules available.

1. Add puma to your Gemfile:

NOTICE: Ignore this entire step 1 for Rails 5+, as Puma is the default webserver.

cd /var/www/site-1
nano Gemfile
source 'https://rubygems.org'
gem 'puma'
bundle install

2. Create/replace contents of puma.rb

First, check how many CPU cores you have:

grep -c processor /proc/cpuinfo

Set ./config/puma.rb:

# Change to match your CPU core count
workers 2

# Min and Max threads per worker
threads 1, 6

# Project folder
app_dir = File.expand_path("../..", __FILE__)

# Default to production
rails_env = ENV.fetch("RAILS_ENV") { "production" }
environment rails_env

# Set up socket location
bind "unix://#{app_dir}/tmp/puma/puma.sock"

# Logs
stdout_redirect "#{app_dir}/log/puma.stdout.log", "#{app_dir}/log/puma.stderr.log", true

# Set master PID and state locations
pidfile "#{app_dir}/tmp/puma/pid"
state_path "#{app_dir}/tmp/puma/state"

# Establish db connection for new processes
on_worker_boot do
  require "active_record"
  ActiveSupport.on_load(:active_record) do

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

# Puma control rack application

Adjust the number of workers according to the number of CPU cores of your server.

3. Create a ./tmp/puma folder:

sudo mkdir -p ./tmp/puma

This folder will contain the PID/state/socket files. These file paths are set on puma.rb and are expected to be defined as this on the init scripts.

4. Permissions: Add your user to www-data group

Add your own user to the www-data group in order to write/read the project folder:

sudo usermod -a -G www-data daniel

Check your groups:

(out)uid=1000(daniel) gid=1000(daniel) groups=1000(daniel),4(adm),24(cdrom),27(sudo),30(dip),33(www-data),46(plugdev),118(lpadmin),129(sambashare),1001(rvm)

If the www-data group doesn’t appear, do a logout/login. Close/open shell won’t work.

5. Permissions: Set group permissions

Set group ID on the directory (all new files and sub-directories created within the current directory inherit the group ID):

sudo chmod g+s /var/www/site-1
sudo find /var/www/site-1 -type d -exec chmod 2775 {} \;

Set write permissions to the group:

sudo chown -R www-data:www-data /var/www/site-1
sudo chmod -R g+w /var/www/site-1
sudo find /var/www/site-1 -type f -exec chmod ug+rw {} \;

6. Download init scripts (Jungle):

cd ~
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/init.d/puma
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/init.d/run-puma

7. Install init scripts:

# Copy the init script to services directory 
sudo mv puma /etc/init.d
sudo chmod +x /etc/init.d/puma
# Make it start at boot time. 
sudo update-rc.d -f puma defaults
# Move the Puma runner to an accessible location
sudo mv run-puma /usr/local/bin
sudo chmod +x /usr/local/bin/run-puma
# Create an empty configuration file
sudo touch /etc/puma.conf

8. Set which projects will be managed by Jungle


9. Start Puma (Jungle) service:

sudo service puma restart

Check socket:

ls -lh /var/www/site-1/tmp/puma/puma.sock

If the file doesn’t exist, check puma logs (./log/puma*)

10. Test if service can be stopped:

To be able to stop / restart the service, root user must have Ruby and Puma installed:

sudo pumactl -v

If you see the “command not found” error, you need to install Puma on the root user:

A quick fix is to run “sudo apt install puma”. This won’t mess your rbenv/rvm.

11. Install mod_proxy and mod_proxy_http:

sudo a2enmod proxy
sudo a2enmod proxy_http
sudo service apache2 reload

12. Edit your domain #1 configuration:

Notice that “ProxyPass” ignores “assets” and “system” folders, so they won’t be processed by Ruby.

13. Enable your domain #1 and restart Apache:

sudo a2ensite site-1
apachectl -t
(out)Syntax Ok
sudo /etc/init.d/apache2 restart

OPTIONAL: Using a different user other than www-data for Puma processes:

As an example, let’s create a deploy user for the Puma processes.

All commands as root user:


Create the user/group:

# create user deploy (password 102030)
useradd -m deploy -p 102030
# create group deploy
groupadd deploy
# add user to the group
usermod -a -G deploy deploy

Add it to RVM (if using RVM):

rvm group add rvm deploy

Change folder ownership:

chown -R deploy:deploy /var/www/site-1

Change /etc/puma.conf:


Reload service:

service puma restart

HINT: For extra security, you can set up a different user for each Rails project.

That’s it. Now you can add another site just repeating the process.

Close Menu