How To Easily Build a Fast, Reliable Production Rails 3.2 Web Server with Ubuntu 12.10 / Nginx / Passenger
Bonus: This server will work with multiple virtual hosts!!
Just one note before we start. This took MANY, MANY tries to get right. I just went all the way through it myself from start to finish and it worked. There were a few issues with my test app itself that I had to troubleshoot, but I finally got them worked out. See the “Deploy Source Code” section for some problem solving tips.
Before You Start
- Set up a fresh Virtual Machine, ready for OS Install (or Ubuntu 12.10 pre-installed).
- Download latest Ubuntu Server install .iso (if not pre-installed).
- Sign in to your Github account in a browser window.
- Create a fresh document to keep notes (Use Evernote or a Google Doc so you can always refer back to it).
- Write down the following information: Your hostname and hostname.domain.com, and your IP address(es). Be sure to make notes of each password you use, and any deviations you make from the basic instructions here.
Install Ubuntu 12.10 Server and create user deployer
- Install Ubuntu as normal. If you can, set up your user account as “deployer” – we will need to use it later.
- Select to install only the OpenSSH Server feature – we will install all the necessary software as we proceed. If you could install OpenSSH as the server was initially set up, do it now.
1sudo apt-get install openssh-server - (Secret tip here – if you’re using VMWare Virtualization and get repeated characters when typing in the console, follow these simple steps to fix it).
- Using the console, edit the hostname, if necessary.
1sudo nano /etc/hostname - Using the console, edit or add your IP Address(es), if necessary.
12sudo nano /etc/network/interfacessudo restart networking - If you are using VMWare, you can take a convenient snapshot of the VM at this point, to allow you to quickly roll back to this point in the process at any time. Shut down, Take a Snapshot, then power the VM back on.
1sudo poweroff - You should now be able to ssh into the Virtual Machine from your local system.
1ssh deployer@hostname.domain.com - Use my guide to Install VMWare Tools, if you’re using VMWare Virtualization like me.
- Update the system.
12sudo apt-get -y updatesudo apt-get -y upgrade - Make sure you install all the software on the apt-get install line here (scroll to the right in the box below). It will all be needed.
12sudo apt-get -y install linux-headers-server build-essential ntp ntpdate git-core g++ curl libcurl4-openssl-dev libssl-dev zlib1g-dev libreadline-dev libyaml-dev libxslt1-dev htop apache2-utils python make make-doc software-properties-commonsudo reboot - If you haven’t already, set up the deployer account and group that will own the site code.
1234567sudo useradd -s /bin/bash -m deployer (if it doesn't already exist)sudo passwd deployer (if it doesn't already exist)sudo usermod -a -G sudo deployer (you will already be in the group sudo if you set the user up during initial Ubuntu install)sudo groupadd wwwadminssudo usermod -a -G wwwadmins deployersu deployer (or just ssh into the server as deployer)cd ~ - Create a directory for the site files.
1sudo mkdir -p /var/www/production - Give deployer ownership over these directories.
1sudo chown deployer:wwwadmins /var/www/production - Edit the GRUB configuration to prevent the server going to sleep. Setting ACPI off means that Ubuntu will not attempt to use ACPI features like Power Saving, Standby, and Hibernate among others.
1sudo nano /etc/default/grub - On the line GRUB_CMDLINE_LINUX_DEFAULT=”…”, add acpi=off and save the file.
1GRUB_CMDLINE_LINUX_DEFAULT="acpi=off" - Update GRUB.
1sudo update-grub - Note: After I updated GRUB, my system booted into the GRUB console boot screen on the next reboot. I had to select “Ubuntu” and then it booted fine every time after that.
Set up SSH
- Generate your public ssh key (accept default location ~/.ssh folder and leave passwords blank).
1ssh-keygen - Print the key to the screen, copy into your clipboard, and make a note of it in your working document.
1cat .ssh/id_rsa.pub - Speed up SSH by telling it not to use DNS lookups.
1sudo nano /etc/ssh/sshd_config - At the end of the file, add this line, then save
1UseDNS no - Optional: If you’re using Github, sign in now, Edit your Profile, click on SSH Keys, Add SSH Key (paste in the public key from ~/.ssh/id_rsa.pub and give it the name of your server).
Install NodeJS
- Install Latest NodeJS for use with the rails asset pipeline.
123sudo add-apt-repository ppa:chris-lea/node.jssudo apt-get -y updatesudo apt-get -y install nodejs - Install Node Package Manager (npm).
1curl https://npmjs.org/install.sh | sudo sh
Install Ruby
- Become deployer.
12su deployercd ~ - Install rbenv. You can copy and paste this whole block into your terminal session at once. This step may take a while (10 minutes or so) and you may need to hit enter one last time to accept the last command (I did).
123456789101112cd ~git clone git://github.com/sstephenson/rbenv.git .rbenvecho 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrcecho 'eval "$(rbenv init -)"' >> ~/.bashrcexec $SHELL # Restart the shellmkdir -p ~/.rbenv/pluginscd ~/.rbenv/pluginsgit clone git://github.com/sstephenson/ruby-build.gitgit clone git://github.com/sstephenson/rbenv-gem-rehash.gitrbenv install 1.9.3-p385rbenv rehashrbenv global 1.9.3-p385 - Edit .bashrc
1nano ~/.bashrc - Add this block at the top of the file BEFORE “# If not running interactively, don’t do anything”
12345export RBENV_ROOT="${HOME}/.rbenv"if [ -d "${RBENV_ROOT}" ]; thenexport PATH="${RBENV_ROOT}/bin:${PATH}"eval "$(rbenv init -)"fi - Reload .bashrc (careful here – this command starts with a dot . ).
1. ~/.bashrc - Check that the ruby version is the one you just installed.
1ruby -v - Go home.
1cd ~ - Install rails.
1gem install rails --no-ri --no-rdoc - Install bundler.
1gem install bundler --no-ri --no-rdoc - Rehash.
1rbenv rehash - Dump out your gem environment and copy into your doc for safekeeping. It could be useful for troubleshooting later.
1gem env
Install Passenger and Configure NGINX
- Install the passenger gem
1gem install passenger --no-ri --no-rdoc - Find the full path to this gem you just installed.
1which passenger-install-nginx-module - Use passenger to install NGINX – you must use the full path.
1sudo /home/deployer/.rbenv/shims/passenger-install-nginx-module - Follow the prompts. Select 1. Yes: download, compile and install Nginx for me. (recommended)
- Write down your nginx path, by default it’s /opt/nginx
- Create virtual hosts directory structure
1sudo mkdir -p /opt/nginx/conf/sites-available /opt/nginx/conf/sites-enabled - Manually create an upstart script for NGINX
1sudo nano /etc/init/nginx.conf
12345678description "Nginx HTTP Server"start on filesystemstop on runlevel [!2345]respawnexec /opt/nginx/sbin/nginx -g "daemon off;" - Edit NGINX config
1sudo nano /opt/nginx/conf/nginx.conf - Add this line just below the server { } block (just above “# HTTPS server”)
1include /opt/nginx/conf/sites-enabled/*; - Enable GZIP compression like this in the nginx.conf file.
123456789101112131415161718192021222324252627282930# Enable Gzip:gzip on;gzip_http_version 1.0;gzip_comp_level 7;gzip_min_length 512;gzip_buffers 16 8k;gzip_proxied any;gzip_types# text/html is always compressed by HttpGzipModuletext/csstext/plaintext/x-componentapplication/javascriptapplication/jsonapplication/xmlapplication/xhtml+xmlapplication/x-font-ttfapplication/x-font-opentypeapplication/vnd.ms-fontobjectimage/svg+xmlimage/x-icon;# This should be turned on if you are going to have pre-compressed copies (.gz) of# static files available. If not it should be left off as it will cause extra I/O# for the check. It would be better to enable this in a location {} block for# a specific directory:# gzip_static on;gzip_disable "msie6";gzip_vary on; - Start NGINX
1sudo service nginx start - You may want to tune NGINX using the HTML5 Boilerplate Server Configs. Also, do yourself a favor and check out this amazing page about NGINX server configs, along with actually good explanations of all the configuration settings in plain English.
Install your Database Server (if necessary)
- We’ll use MySQL.
1sudo apt-get -y install libmysqlclient-dev mysql-server
Enter root password twice when prompted. - Secure the installation.
1mysql_secure_installation
Change the root password? [Y/n] n
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y - Log in to mysql
1mysql -uroot -pyourpassword - Create a database. Make sure the database name doesn’t have any dashes in it.
1CREATE DATABASE appname_production; - Create a production user with all privileges.
12GRANT ALL PRIVILEGES ON *.* TO 'deployer'@'localhost' IDENTIFIED BY 'some_pass' WITH GRANT OPTION;FLUSH PRIVILEGES; - Exit the mysql session.
1exit - Update the production block in your source code /config/database.yml to use deployer, the correct password, and the correct socket for Ubuntu.
12345database: appname_productionpool: 5username: deployerpassword: some_passsocket: /var/run/mysqld/mysqld.sock - Important: Commit and Push your source code changes to github.
123git add .git commit -am "Updated database.yml for production deployment with Capistrano."git push origin master
Create your nginx Config File
- Use nano to edit and save the file.
1sudo nano /opt/nginx/conf/sites-available/sitename - Adapt and save this config file with your settings.
- Enable the site.
1sudo ln -s /opt/nginx/conf/sites-available/sitename /opt/nginx/conf/sites-enabled/sitename - Stop and start NGINX.
12sudo service nginx stopsudo service nginx start
(Option 1 – My Choice) Deploy Source Code with Capistrano
- Uncomment gem ‘capistrano’ in your source code and run the bundle command.
- Initialize Capistrano.
12cd /path/to/your/sourcecapify . - Edit your brand new /Capfile and uncomment load ‘deploy/assets’.
- Edit your config/deploy.rb, filling in the correct information on each line:
1234567891011121314151617181920212223242526272829303132333435363738394041require "bundler/capistrano"#load 'lib/deploy/seed' #include if you need to load seed data with cap deploy:seedserver "hostname.domain.com", :app, :web, :db, :primary => trueset :user, "deployer" # The server's user for deploysset :scm_passphrase, "yourpassword" # The deploy user's passwordset :application, "appname.com"set :use_sudo, falsedefault_environment["GEM_PATH"] ="/home/deployer/.rbenv/versions/1.9.3-p385/lib/ruby/gems/1.9.1:/home/deployer/.rbenv/shims/ruby"default_environment["PATH"] = "$HOME/.rbenv/shims:$HOME/.rbenv/bin:$HOME/.rbenv/versions/1.9.3-p385/lib/ruby/gems/1.9.1:$PATH"set :deploy_to, "/var/www/production/appname"set :deploy_via, :remote_cacheafter "deploy", "deploy:cleanup" # keep only the last 5 releasesset :scm, "git"set :scm_verbose, trueset :repository, "git@github.com:githubusername/repositoryname.git"set :branch, "master"default_run_options[:pty] = true # Must be set for the password prompt from git to workssh_options[:forward_agent] = truenamespace :deploy do# If you need to load seed data. Syntax: cap deploy:seeddesc "Reload the database with seed data"task :seed dorun "cd #{current_path}; bundle exec rake db:seed RAILS_ENV=#{rails_env}"endtask :start do ; endtask :stop do ; endtask :restart, :roles => :app, :except => { :no_release => true } dorun "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}" #restarts nginxendend - Validate your Capistrano Recipe. You’ll need to supply your deployer password.
1cap deploy:setup - Check that everything is set up correctly.
1cap deploy:check - Cold Deploy.
1cap deploy:cold - I had to do an additional regular/everyday deployment to get the assets to precompile.
1cap deploy - Seed the database (optional).
1cap deploy:seed
(Option 2) Non-Capistrano Deploy: Install your Site Source Code
- Become deployer
1su deployer
- Change Directory
1cd /var/www/production
- Clone your app’s git repository
1git clone https://github.com/github_username/repository_name.git
- Run bundler
1bundle
- Create and migrate your database
1RAILS_ENV=production rake db:create db:schema:load
Hit the Production URL and Troubleshoot any problems
- Open the Production URL in your browser.
1http://hostname.domain.com/ - If NGINX doesn’t show your site, look in the NGINX log and try to fix whatever problem it shows.
1tail -n50 /opt/nginx/logs/error.log - If the app doesn’t start up properly, check and/or watch the production log as you work through the problems.
12cd /var/www/production/appname/current/logtail -f -n50 production.log - For me, I had the server complain about some files not being precompiled in the production.log file. If you have manual javascripts or css that need precompilation, edit your /config/environments/production.rb file and uncomment/modify the following line to include your custom assets.
1config.assets.precompile += ['modernizr.js'] - For future Capistrano deploys, just run this command from your app directory.
1cap deploy
Install Monit (Optional)
I find this serves two purposes: It helps me monitor the server, and it keeps the server more responsive and less apt to fall asleep.
- Follow my guide to installing Monit, except use this config file – make changes where necessary. I know it’s long, but look for and modify for your configuration the blocks that are NOT commented out, and search for “pa$$word”.
- This configuration does not monitor Passenger. If you’d like to attempt it, try the passenger_monit gem.
Last Word
If anybody has improvements to these steps, PLEASE leave notes in the comments.
I would love to keep this as efficient and straightforward as possible, as well as up to date as new bits are released.
Let me know if this worked for you!
Also, please follow me on Twitter: @justinschier










by Justin Schier, Chief Creative Officer
Ok, this one is kind of gross. But try it out and you will thank me.

