We will use rbenv to be our Ruby version control. Let's update our app and then get the dependencies we need for our server.
sudo apt-get update
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev
Once we have those dependencies, let's install rbenv.
cd ~
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
If you are using Ubuntu Desktop, instead of using .bash_profile use .bashrc.
Now that we have rbenv installed in our home directory of our current user, it's time to bring in the big guns.
Let's install the most recent version of ruby to date.
rbenv install -v 2.4.1
rbenv global 2.4.1
Using rbenv global will set our ruby version to the version we chose on all of our shells.
Let's verify that the Ruby version we chose is now the one we are using.
ruby -v
If you don't want Ruby Gems to install documentation on your production server, run the following command.
echo "gem: --no-document" > ~/.gemrc
Now let's install bundler and rails.
gem install bundler
gem install rails
We now need to run a rehash so that rbenv loads our most current content.
rbenv rehash
Let's also verify that Rails was installed correctly and with what version.
rails -v
If you receive the version, it was installed successfully.
A few Rails features, such as the Asset Pipeline, depend on a Javascript runtime. We will install Node.js to provide this functionality.
sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs
Congrats! We now have everything installed on our system.
Rails uses sqlite3 as its default database, so let's get rid of that crap and get a better one.
sudo apt-get install mysql-server mysql-client libmysqlclient-dev
gem install mysql2
If you haven't created or cloned an app yet, let's do so now.
rails rew AppName -d mysql
Don't forget to run the following command and paste your encrypted secret key in your config/database.yml file for your production key.
rake secret
Before we move on, let's create our mysql user so we can apply it to our config/database.yml. Follow my other guide here to do so. Paste the username and password in the said config file so that our app will now use the newly created user. It would be good practice to name the user similar to your app name. You may also want do download the Dotenv ruby gem to store your credentials in, so they don't get pushed up to the version control you are using.
Let's create a production database which will be used later on and also set up the production side of the server.
rails db:create RAILS_ENV=production
rails db:migrate RAILS_ENV=production
rails assets:precompile RAILS_ENV=production
Let's make sure Puma is in our Gemfile.
vim Gemfile
If not we will add it.
gem 'puma'
Then run bundle install.
bundle install
We are going to need to know how many processor cores we have to config puma with, you can find out your current setup with the following command.
grep -c processor /proc/cpuinfo
Now let's add our config to our puma file.
vim config/puma.rb
Add the following code into it below.
# Change to match your CPU core count
workers 2
# 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
Make sure to change the workers to how many cpu cores your computer has. Save the file and then exit out. We are now going to create the files we mentioned above in the code snippet.
mkdir -p shared/pids shared/sockets shared/log
Let's create a script now so that we can start and stop puma with ease.
cd ~
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma-manager.conf
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma.conf
Now we need to update our puma.conf with our current ubuntu user that will be doing all the work.
vim puma.conf
Replace the following section in the file with your deploy user's name.
setuid YourUser
setgid YourUser
Save and exit. We are now going to copy the script into our /etc/init directory.
sudo cp puma.conf puma-manager.conf /etc/init
The puma-manager.conf script refers to /etc/puma.conf for the applications that it should handle. Let's create that handle file.
sudo vim /etc/puma.conf
Each line in the file should reference a path that you want the puma manger to handle. You simple just put the path to each of your projects in the format below on a separate line.
/path/to/appname
After you save and exit, your application will now start on Upstart, which means that after a reboot, or anything else, your server will automatically restart.
Here are some commands to manually start using puma manger.
sudo start puma-manager
sudo stop puma-manager
sudo restart puma-manager
# For a single server startup
sudo start puma app=/path/to/appname
Our rails production setup is now ready and is listening through puma's shared/sockets/puma.sock socket. There is just one more step to get our server to be accessible to the outside world, we now have to set up a Nginx reverse proxy.
Install Nginx with the following command.
sudo apt-get install nginx
Now let's edit the default site available config to get our app up and running.
sudo vim /etc/nginx/sites-available/default
Your file is going to have already default config, replace it with the following code. If you are wanting ssl, you will have to do it differently. This setup is for only port 80, http. For https, I will be writing another guide for how to set that up.
upstream app {
server unix:/path/to/appname/shared/sockets/puma.sock fail_timeout=0;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
root /path/to/appname/public;
try_files $uri/index.html $uri @app;
location @app {
proxy_pass http://app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name www.app.com;
ssl on;
ssl_certificate /etc/letsencrypt/live/app.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/app.com/privkey.pem; # managed by Certbot
ssl_protocols TLSv1.1 TLSv1.2 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_dhparam /home/ubuntu/ssl_certs/dhparam.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
return 301 https://app.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name app.com;
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
ssl on;
ssl_certificate /etc/letsencrypt/live/app.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/app.com/privkey.pem; # managed by Certbot
ssl_protocols TLSv1.1 TLSv1.2 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_dhparam /home/ubuntu/ssl_certs/dhparam.pem;
root /home/ubuntu/app/public;
try_files $uri/index.html $uri @app;
location @app {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host:443;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-Proto https;
}
location /cable {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
}
location ^~ /.well-known/ {
root /usr/share/nginx/html;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d;
}
}
After pasting this code, exit and save. Then we will restart the Nginx server and our website will now be fully functional!
sudo service nginx restart
Now visit your public ip address.
http://public_ip_address_or_domain_name
Make sure gzip is enabled for compression. Feel free to enable additional settings.
sudo vim /etc/nginx/nginx.conf
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# gzip_vary on;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
If you find your nginx outdated after a while, update it.
sudo add-apt-repository ppa:nginx/stable
sudo apt-get update
sudo apt-get install nginx
sudo apt-get install sshguard
This monitors your system and protects against brute force attacks and blocks users ip address with a firewall rule if it notices any suspicious activity.
[Unit]
Description=Puma HTTP Server
After=network.target
# Uncomment for socket activation (see below)
#Requires=puma.socket
[Service]
# Foreground process (do not use --daemon in ExecStart or config.rb)
Type=simple
# Preferably configure a non-privileged user
User=ubuntu
Group=ubuntu
# Specify the path to your puma application root
WorkingDirectory=/home/ubuntu/downloadsecurely
# Helpful for debugging socket activation, etc.
# Environment=PUMA_DEBUG=1
# EnvironmentFile=/home/deployer/app/.env
# The command to start Puma
# ExecStart=<WD>/sbin/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
#ExecStart=/usr/local/bin/bundle exec --keep-file-descriptors puma -e production
#ExecStart=/home/ubuntu/.rbenv/shims puma -C /home/ubuntu/downloadsecurely
#ExecStart=/home/ubuntu/downloadsecurely/bin bundle exec puma --daemon
#ExecStart=/home/ubuntu/.rbenv/versions/2.5.0/bin bundle exec puma -C /home/ubuntu/downloadsecurely/config/puma.rb --daemon
#ExecStart=/home/ubuntu/downloadsecurely bundle exec puma
#ExecStart=/home/ubuntu/.rbenv/shims bundle exec puma -C /home/ubuntu/downloadsecurely/config/puma.rb --daemon
#ExecStop=/home/ubuntu/.rbenv/shims bundle exec pumactl -S /home/ubuntu/downloadsecurely/shared/tmp/pids/puma.state stop
#PIDFile=/home/ubuntu/downloadsecurely/shared/tmp/pids/puma.pid
ExecStart=/home/ubuntu/.rbenv/bin/rbenv exec bundle exec puma -C /home/ubuntu/downloadsecurely/config/puma.rb
ExecStop=/home/ubuntu/.rbenv/bin/rbenv exec bundle exec pumactl -S /home/ubuntu/downloadsecurely/shared/tmp/pids/puma.state stop
Restart=always
[Install]
WantedBy=multi-user.target
# After installing or making changes to either puma.socket or
# puma.service.
systemctl daemon-reload
# Enable both socket and service so they start on boot. Alternatively
# you could leave puma.service disabled and systemd will start it on
# first use (with startup lag on first request)
systemctl enable puma.socket puma.service
# Initial start up. The Requires directive (see above) ensures the
# socket is started before the service.
systemctl start puma.socket puma.service
# Check status of both socket and service.
systemctl status puma.socket puma.service
# A "hot" restart, with systemd keeping puma.socket listening and
# providing to the new puma (master) instance.
systemctl restart puma.service
# A normal restart, needed to handle changes to
# puma.socket, such as changing the ListenStream ports. Note
# daemon-reload (above) should be run first.
systemctl restart puma.socket puma.service
sudo vim /etc/systemd/system/puma.service