Installing and Configuring Nginx with Puma + MySQL for Rails on Debian Jessie

Hey guys, here I will show you how to configure the Nginx with Puma and MySQL to host a Rails application.

I will consider that you have a minimal Debian Jessie installation, so we need to install some dependencies and prepare the system.

Let's configure the repositories

vi /etc/apt/sources.list
# OFFICIAL REPOSITORY
deb http://httpredir.debian.org/debian jessie main contrib non-free
deb-src http://httpredir.debian.org/debian jessie main contrib non-free

# SECURITY UPDATE REPOSITORY
deb http://security.debian.org/ jessie/updates main contrib non-free
deb-src http://security.debian.org/ jessie/updates main contrib non-free

# PROPOSE UPDATE REPOSITORY
deb http://httpredir.debian.org/debian jessie-proposed-updates main contrib non-free
deb-src http://httpredir.debian.org/debian jessie-proposed-updates main contrib non-free

Now we need to update the repositories

apt-get update

Now we need to install the Nginx and the git

apt-get install curl vim git-core nginx -y

Now let's update the repositories

apt-get update

Now let's install the MySQL

apt-get install mysql-server mysql-client libmysqlclient-dev libmysql++-dev libmysqld-dev -y

Note: You will be asked about the root password, so set it up.

Let's import the rvm gpg key

gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

Now let's install the rvm to manage our Rubies

curl -sSL https://get.rvm.io | bash -s stable

Now if you have more users that will be using the rvm we need to add them into the rvm's group

gpasswd -a douglas rvm

Now we need to import the environment variables

source /etc/profile.d/rvm.sh

Now let's install the dependencies of the rvm

rvm requirements

Now we need to install the ruby

rvm install 2.3.0

Now we need to set as the default

rvm use 2.3.0 --default

Now we need to install the rails and the bundler

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

Three flags were used:

  • -V (Verbose Output): Prints detailed information about Gem installation
  • --no-ri - (Skips Ri Documentation): Doesn't install Ri Docs, saving space and making installation fast
  • --no-rdoc - (Skips RDocs): Doesn't install RDocs, saving space and speeding up installation

Note: You can also install a specific version of Rails according to your requirements by using the -v flag:

gem install rails -v '4.2.0' -V --no-ri --no-rdoc

Now let's clone a project to use as test

cd /var/www/html && git clone https://github.com/douglasqsantos/dqs-rails-base.git

Now let's access the project

cd dqs-rails-base/

Now we need to add the puma into the Gemfile and change the sqlite3 to MySQL

vim Gemfile
ruby '2.3.0'
[...]
# Use sqlite3 as the database for Active Record
#gem 'sqlite3'
gem 'mysql2'
[...]
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

# app server
gem 'puma'
[...]

Now let's run the bundler, it will take a little while.

bundle install

Now we need to create the production key to run the application

rake secret
46d1feba0000226fdf16e9bd94739c7a09fd24401ff8ba33945161fe613a0211459a9167b6267eb796b66167ddf187f4a40a44460a46a9adfeecd875161ea5ac

Now we need to pass it into config/secrets.yml

vim config/secrets.yml
[...]
production:
  secret_key_base: b98f316c6a54b6c76ab01859945b78aef9ec0e051e55a30b530957441420057ab3e42fcff7fa308a488a689ee61f10e10ccc07040b190d839f96c7803f50e078

Now we need to compile the assets

RAILS_ENV=production rake assets:precompile

Now let's configure the database connection

vim config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: dqs_rails
  password: 'dqs_rails_passwd'
  port: 3306

development:
  <<: *default
  database: dqs_rails_base_dev

test:
  <<: *default
  database: dqs_rails_base_test

production:
  <<: *default
  database: dqs_rails_base_prod

Now let's create the database

mysql -u root -p
CREATE DATABASE dqs-rails-base-prod;
GRANT ALL PRIVILEGES ON dqs_rails_base_prod.* to dqs_rails@localhost IDENTIFIED BY 'dqs_rails_passwd';
FLUSH PRIVILEGES;
quit

Now we can migrate the database

RAILS_ENV=production rake db:migrate

After migrate the database we can seed the database

RAILS_ENV=production rake db:seed --trace

Now let's get rid of some warnings that will be generate by the rvm about the ruby version

rvm rvmrc warning ignore allGemfiles

Before configuring Puma, you should look up the number of CPU cores your server has. You can easily to that with this command:

grep -c processor /proc/cpuinfo

Now we need to configure the puma server

vim config/puma.rb
# Change to match your CPU core count
workers 4

# Min and Max threads per worker
threads 1, 6

app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/shared"

# Default to production
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env

# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"

# Logging
stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true

# Set master PID and state locations
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
activate_control_app

on_worker_boot do
  require "active_record"
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env])
end

Now create the directories that were referred to in the configuration file:

mkdir -p shared/pids shared/sockets shared/log

Now let's start the puma server

cd /var/www/html/dqs-rails-base && ( export RACK_ENV="production" ; /usr/local/rvm/bin/rvm default do bundle exec puma -C /var/www/html/dqs-rails-base/config/puma.rb --daemon )

To stop the puma server

cd /var/www/html/dqs-rails-base && ( export RACK_ENV="production" ; rvm all do bundle exec pumactl -S /var/www/html/dqs-rails-base/shared/pids/puma.state stop )

Getting the vim nginx syntax

wget -c https://raw.githubusercontent.com/douglasqsantos/easy-debian/master/nginx.vim -O /usr/share/vim/vim74/syntax/nginx.vim

Now we need to create the configuration for Nginx

vim config/nginx.conf
# Puma configuration
upstream puma {
  server unix:///var/www/html/dqs-rails-base/shared/sockets/puma.sock;
}

# Default server configuration
server {
  listen 80 default_server deferred;
  server_name rails.douglasqsantos.com.br;
  server_tokens off;

  root /var/www/html/dqs-rails-base/public;
  access_log /var/www/html/dqs-rails-base/log/nginx.access.log;
  error_log /var/www/html/dqs-rails-base/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;
}

Now let's remove the default site for Nginx

rm /etc/nginx/sites-enabled/default
ln -nfs "/var/www/html/dqs-rails-base/config/nginx.conf" "/etc/nginx/sites-enabled/rails"

Now let's start the Puma server

cd /var/www/html/dqs-rails-base && ( export RACK_ENV="production" ; /usr/local/rvm/bin/rvm default do bundle exec puma -C /var/www/html/dqs-rails-base/config/puma.rb --daemon )

Now let's restart the Nginx Server

systemctl restart nginx

Now we can check the status of the service

systemctl status nginx

Now we can check the status of the app http://ip_server or http://rails.douglasqsantos.com.br

Now let's create a script to handle the puma service

vim /var/www/html/dqs-rails-base/bin/handle_puma.sh
#!/bin/bash
#-------------------------------------------------------------------------
# Name: /var/www/html/dqs-rails-base/bin/handle_puma.sh
#
# Site: http://wiki.douglasqsantos.com.br
# Autor: Douglas Quintiliano dos Santos <douglas.q.santos@gmail.com>
# Management: Douglas Quintiliano dos Santos <douglas.q.santos@gmail.com>
#
#-------------------------------------------------------------------------
# License: [MIT license](http://opensource.org/licenses/MIT)
#
#-------------------------------------------------------------------------

### CORES UTILIZADAS NO SCRIPT ###
GREY="\033[01;30m"
RED="\033[01;31m"
GREEN="\033[01;32m"
YELLOW="\033[01;33m"
BLUE="\033[01;34m"
PURPLE="\033[01;35m"
CYAN="\033[01;36m"
WHITE="\033[01;37m"
CLOSE="\033[m"

case $1 in
    start)
        echo -e "${GREEN}[        Starting Puma         ]${CLOSE}"
        cd /var/www/html/dqs-rails-base && ( export RACK_ENV="production" ; /usr/local/rvm/bin/rvm default do bundle exec puma -C /var/www/html/dqs-rails-base/config/puma.rb --daemon )
        echo -e "${GREEN}[        Puma Started          ]${CLOSE}"
    ;;
    stop)
        echo -e "${RED}[        Stopping Puma         ]${CLOSE}"
       cd /var/www/html/dqs-rails-base && ( export RACK_ENV="production" ; rvm all do bundle exec pumactl -S /var/www/html/dqs-rails-base/shared/pids/puma.state stop )
        echo -e "${RED}[        Puma Stopped Puma     ]${CLOSE}"
    ;;
    restart)
        echo -e "${GREEN}[        Restarting Puma       ]${CLOSE}"
          $0 stop
          $0 start
        echo -e "${GREEN}[        Puma Restarted        ]${CLOSE}"
    ;;
    *)
      echo -e "${RED} Valid Options: (start|stop|restart) ${CLOSE}"
    ;;
esac

Now we need to give the correct permission to the script

chmod +x /var/www/html/dqs-rails-base/bin/handle_puma.sh

Now we can call the script such as

/var/www/html/dqs-rails-base/bin/handle_puma.sh start
/var/www/html/dqs-rails-base/bin/handle_puma.sh stop
/var/www/html/dqs-rails-base/bin/handle_puma.sh restart

Let's create the service

vim /etc/systemd/system/puma.service
[Unit]
Description=Puma Control
After=network.target auditd.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/etc/puma/puma-start
ExecStop=/etc/puma/puma-stop

[Install]
WantedBy=multi-user.target

Now we need to create the directory that will store the scripts

mkdir /etc/puma

Now let's create the script that will start the service

vim /etc/puma/puma-start
#!/bin/bash

cd /var/www/html/dqs-rails-base && ( export RACK_ENV="production" ; /usr/local/rvm/bin/rvm all do bundle exec puma -C /var/www/html/dqs-rails-base/config/puma.rb --daemon )

Now let's give the permission to the script

chmod +x /etc/puma/puma-start

Now let's create the script that will stop the service

vim /etc/puma/puma-stop
#!/bin/bash

cd /var/www/html/dqs-rails-base && ( export RACK_ENV="production" ; /usr/local/rvm/bin/rvm all do bundle exec pumactl -S /var/www/html/dqs-rails-base/shared/pids/puma.state stop )

Now let's give the permission to the script

chmod +x /etc/puma/puma-stop

Now let's enable the service

systemctl enable puma

Now we can handle the puma service with the systemd

systemctl start puma
systemctl stop puma
systemctl restart puma
systemctl status puma

Now we can restart the service and check if everything is working properly.

Now let's enable the https

Let's create the directory to store the certificates

mkdir /var/www/html/dqs-rails-base/config/ssl

Let's access the directory

cd /var/www/html/dqs-rails-base/config/ssl

Let's create the certificate key

openssl genrsa -des3 -out server.key 1024
Generating RSA private key, 1024 bit long modulus
..............................++++++
.++++++
e is 65537 (0x10001)
Enter pass phrase for server.key: #PASSWORD
Verifying - Enter pass phrase for server.key: #PASSWORD

Now we need to create the certificate sign request

openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key: #PASSWORD
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:BR
State or Province Name (full name) [Some-State]:Parana
Locality Name (eg, city) []:Curitiba
Organization Name (eg, company) [Internet Widgits Pty Ltd]:DQS
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:rails.douglasqsantos.com.br
Email Address []:douglas.q.santos@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:DQS

Now we need to change the certificate permissions

chmod 0400 server.*
cp server.key server.key.orig

Now we need to auto sign the certificate

openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
Signature ok
subject=/C=BR/ST=Parana/L=Curitiba/O=DQS/OU=IT/CN=rails.douglasqsantos.com.br/emailAddress=douglas.q.santos@gmail.com
Getting Private key
Enter pass phrase for server.key: #PASSWORD

Now let's remove the password from our certificate, otherwise we need to put the password every nginx start.

openssl rsa -in server.key.orig -out server.key
Enter pass phrase for server.key.orig: #PASSWORD
writing RSA key

Let's change the certificates permissions.

chmod 0400 /var/www/html/dqs-rails-base/config/ssl/* 

Now let's create a backup of the nginx configuration

cp -Rfa /var/www/html/dqs-rails-base/config/nginx.conf{,.bkp}

Now let's configure the nginx to use the ssl

vim /var/www/html/dqs-rails-base/config/nginx.conf
# Puma configuration
upstream puma {
  server unix:///var/www/html/dqs-rails-base/shared/sockets/puma.sock;
}

# Default server configuration
server {
  ## Sets the address and port for IP, or the path for a UNIX-domain socket on which the server will accept requests.
  listen 443;
  
  ## Sets names of a virtual server
  server_name rails.douglasqsantos.com.br;
  
  ## Enables or disables emitting nginx version in error messages and in the “Server” response header field.
  server_tokens off;
  
  ## Enables the HTTPS protocol for the given virtual server.
  ssl on;
  
  ## Specifies a file with the certificate in the PEM format for the given virtual server.
  # if you are using an valid certificate need to do the following process to use it.
  # cat server_name.crt CertCA.crt >> server.crt
  ssl_certificate /var/www/html/dqs-rails-base/config/ssl/server.crt;
  
  ## Specifies a file with the secret key in the PEM format for the given virtual server.
  ssl_certificate_key /var/www/html/dqs-rails-base/config/ssl/server.key;

  ### Enable the timout of the ssl session to 5 minutes
  ssl_session_timeout  5m;
  
  ## Specifies the enabled protocols
  ssl_protocols  SSLv2 SSLv3 TLSv1;
  
  ## Specifies the enabled ciphers
  ssl_ciphers  HIGH:!aNULL:!MD5;
  
  ## Specifies the enabled ciphers
  ssl_prefer_server_ciphers   on;

  ## Sets the root directory for requests
  root /var/www/html/dqs-rails-base/public;
  
  ## Logging
  access_log /var/www/html/dqs-rails-base/log/nginx.access.log;
  error_log /var/www/html/dqs-rails-base/log/nginx.error.log info;

  ## Sets configuration for assets
  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_set_header X-Forwarded-Proto https;
    proxy_redirect off;
    proxy_pass http://puma;
  }

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

Now we need to restart the Nginx

systemctl restart nginx

Now we need to restart the Puma

systemctl restart puma

Now we can access https://ip_server or https://rails.douglasqsantos.com.br

References