Tag Archives: Nginx

Generate Gzipped Assets in Rails 4.2 +

Doesn’t Rails generate gzipped assets on running rake assets:precompile? It did so, until Rails 4.2. What changed?

Rails uses sprockets gem, for compiling and serving web assets. Before Sprockets 3 was introduced, rake assets:precompile generated .gz versions for all the assets.

This change, seems backward, for several reasons

  1. Gzipping should always be enabled for static assets. It’s super charges serving of static content.
  2. Assets should be gzip’ed beforehand as part of the build process, rather than letting the webserver do it on the fly, saving on CPU cycles.
  3. Also, Nginx has the ability to serve static .gz files directly, by using the gzip_static on; directive.

The reason for dropping this, was its incompatibility with Apache.

Since Sprockets 3 dropped this feature, it means Rails had to loose it too. The Rails team, plans to bring it back as an opt-in. Nevertheless, what does one do in the meantime?

Here are the options to generate Gzipped Assets with Rails

  1. depends on parallel gem to use multiple CPU cores, but can easily be simplified
  2. gzip assets with Capistrano
  3. write an assets:gzip rake task that plugs into assets:precompile (mentioned below)
namespace :assets do
  desc "Create .gz versions of assets"
  task :gzip => :environment do
    zip_types = /\.(?:css|html|js|otf|svg|txt|xml)$/

    public_assets = File.join(

    Dir["#{public_assets}/**/*"].each do |f|
      next unless f =~ zip_types

      mtime = File.mtime(f)
      gz_file = "#{f}.gz"
      next if File.exist?(gz_file) && File.mtime(gz_file) >= mtime

      File.open(gz_file, "wb") do |dest|
        gz = Zlib::GzipWriter.new(dest, Zlib::BEST_COMPRESSION)
        gz.mtime = mtime.to_i
        IO.copy_stream(open(f), gz)

      File.utime(mtime, mtime, gz_file)

  # Hook into existing assets:precompile task
  Rake::Task["assets:precompile"].enhance do

Setting up Nginx with Unicorn for Rails

I have moved away from using Thin to using Unicorn in production for a number of reasons

Some articles to convince you of the same thing.

  1. Unicorn is Unix
  2. Unicorn Power
  3. Most important for me was the ability of Unicorn master and worker processes to respond to Unix signals. This means, zero downtime deployments.

You need more convincing? Let me know and we can talk!

So with that out of the way, let’s see how you can configure nginx to work with unicorn.

  1. Install nginx via the package manager. (For AMI Ec2:sudo yum install nginx, For Ubuntu : sudo apt-get install nginx)
  2. Use this template for nginx unicorn configuration and then fill in only the required details.
  3. Note that the way it differs from thin configuration is by giving the server value as a location to the socket.
worker_processes 1;
user nobody nogroup;

pid /tmp/nginx.pid;
error_log /tmp/nginx.error.log;

events {
  worker_connections 1024; # increase if you have lots of clients
  # Set this to on if you have more than 1 working processes
  # This will allow only one child to watch the pollset and accept
  # a connection to a socket
  accept_mutex off; # "on" if nginx worker_processes > 1

http {
  include mime.types;
  default_type application/octet-stream;
  access_log /tmp/nginx.access.log combined;

  # This tells Nginx to ignore the contents of a file it is sending
  # and uses the kernel sendfile instead
  sendfile on;

  # Set this to on if you have sendfile on
  # It will prepend the HTTP response headers before
  # calling sendfile()
  tcp_nopush on;

  # This disables the "Nagle buffering algorithm" (Nginx Docs)
  # Good for websites that send a lot of small requests that
  # don't need a response
  tcp_nodelay off;

  gzip on;
  gzip_http_version 1.0;
  gzip_proxied any;
  gzip_min_length 500;
  gzip_disable "MSIE [1-6]\.";
  gzip_types text/plain text/html text/xml text/css
             text/javascript application/x-javascript

  upstream unicorn_server {
   # This is the socket we configured in unicorn.rb
   server unix:/var/rails/testapp/tmp/sockets/unicorn.sock »

  server {
    listen 80;
    client_max_body_size 4G;
    server_name _;
    keepalive_timeout 5;

    # Location of our static files
    root /home/ec2-user/testapp/public;

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;

      # If you don't find the filename in the static files
      # Then request it from the unicorn server
      if (!-f $request_filename) {
        proxy_pass http://unicorn_server;
    }    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /home/ec2-user/testapp/public;

If you have unicorn correctly configured then start unicorn, restart nginx and you should be set.

Setting up Rails with Nginx + Thin on Amazon EC2 with Capistrano

1. Setting up Rails 3.2.12 on EC2 with Amazon Linux AMI

Amazon Linux AMI has Ruby 1.8.7 as default. To install ruby 1.9.3 and Rails 3.2.12 do the following

#Install ruby 1.9 by running 
sudo yum install ruby19

Note that /usr/bin might contain  ‘gem’ which actually refers to 1.8 version. So, in order to install gem1.9 do the following

sudo yum install ruby19-devel

If you do ruby -v you might still see 1.8, in order to change that,

cd /usr/bin
sudo rm ruby
sudo ln -s /usr/bin/ruby1.9 /usr/bin/ruby
sudo ln -s /usr/bin/gem1.9 /usr/bin/gem
ruby -v 
>> ruby 1.9.3p286 (2012-10-12 revision 37165) [x86_64-linux] 

Now that you have ruby 1.9.3 correctly installed, we can install rails.

sudo yum groupinstall "development tools"     #(Download size: 120M !)
gem install rails --no-ri --no-rdoc
rails -v
>> Rails 3.2.12

#Also install appropriate DB that you would be using. Look here if you want to install postgresql on ec2 
#Example, if using sqlite3
sudo yum install sqlite-devel

Congratulations, you have installed Rails 3+ on your Linux AMI.

2. Installing Nginx

sudo yum install nginx

3. Install Thin

gem install thin

4. Configure Git Repository

Let’s say we want to deploy a Rails App called as  rails-demo , for that lets initialize a git-repo as

 mkdir -p ~/git/rails_demo.git
 cd ~/git/rails_demo.git
 git --bare init

This would have initialized a git repository at ~/git/rails_demo.git/ .

5. Add server keys to authorized_keys [Deployment + Git Server === EC2 Linux AMI]

We would need to do this, since Capistrano would be accessing our code-repository as if it were remote, and hence the server-keys would have to be added to its own authorized keys.

test -e ~/.ssh/id_dsa.pub || ssh-keygen -t dsa
cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys

6. Rails Deployment using Capistrano [Local Machine from where you want to push a rails application]

Lets say, we have a bare bones rails app created using only rails new <appname>.

rails new rails_demo
cd rails_demo
# Add the following to the Gemfile
gem 'capistrano'
gem 'thin'
gem 'bigdecimal'
# now run bundle install to install these 2 gems.
bundle install

A) Capify Deploy.rb

#In your rails application directory, run

capify . 

#[add] writing './Capfile'
#[add] writing './config/deploy.rb'
#[done] capified!

Go to deploy.rb and edit it as below. Only change the fields in bold

set :user, 'ec2-user'
set :domain, 'ec2-11-222-33-444.<region>.compute.amazonaws.com'
set :application, "rails_demo"
set :repository, "#{user}@#{domain}:git/#{application}.git"
set :deploy_to, "/home/#{user}/#{application}"
set :normalize_asset_timestamps, false

role :web, domain # Your HTTP server, Apache/etc
role :app, domain # This may be the same as your `Web` serverrole :db, domain, :primary => true # This is where Rails migrations will run
default_run_options[:shell] = false
default_run_options[:pty] = true
# miscellaneous options
set :deploy_via, :remote_cache
set :scm, 'git'
set :branch, 'master'
set :scm_verbose, true
set :use_sudo, false
# Define all the tasks that need to be running manually after Capistrano is finish
namespace :deploy do
task :bundle_install, :roles => :app do
 run "cd #{release_path} && bundle install"
after "deploy:update_code", :bundle_install
 desc "install the necessary prerequisites"
 task :bundle_install, :roles => :app do
 run "cd #{release_path} && bundle install"
 run "cd #{release_path} && rake assets:precompile"end

*Note to change the fields in bold.

B) Thin Configuration

# Generate this thin configuration on local, which default the options to 3 thin 
# servers and production environment
thin config -C config/thin-config.yml -c ~/rails_app --servers 3 -e production

This would generate the config file with details as below. Change the chdir field to /home/ec2-user/rails_demo/current/  . (which is where the app would be deployed on ec2)

 chdir: /home/ec2-user/rails_demo/current/
 environment: production
 port: 3000
 timeout: 30
 log: log/thin.log
 pid: tmp/pids/thin.pid
 max_conns: 1024
 max_persistent_conns: 100
 require: []
 wait: 30
 servers: 3
 daemonize: true

C) Git Usage

Add all these files to git by doing the following

git init .
git add .
git commit -m "rails demo to ec2"
git remote add aws ssh://ec2-user@<ec2-ami-name>/~/git/rails_demo.git/
ssh-add <local-key>
git push aws master

D. Capistrano Deployment

cap deploy:setup
cap deploy:check
cap deploy

If everything goes well, take a break – you deserve it since the deployment has been done and we only need to start / configure nginx now. If you see on the deployment server there would be current directory here

/home/ec2-user/rails_demo/current  – which contains the latest deployment, we did.

7. Configuring + Starting Nginx and Thin

Now, that the application is on the server, let’s start thin and nginx.

cd /home/ec2-user/rails_demo/current 
thin start -C thin-config.yml

(This would start 3 thin instance on ports 3000 , 3001 and 3002 .)

Now, all we need to do is redirect nginx to these ports.

cd /etc/nginx 
vi nginx.conf 

and add the below content at appropriate places. I have only shown the  part to be change in bold of the http section. Only this section needs to be changed for nginx.

http {
 include /etc/nginx/mime.types;
 default_type application/octet-stream;
 server_names_hash_bucket_size 128;
 log_format main '$remote_addr - $remote_user [$time_local] "$request" '
 '$status $body_bytes_sent "$http_referer" '
 '"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
 keepalive_timeout 65;
#gzip on;
upstream thin {
server {
 listen 80;
 server_name ec2-122-248-194-230.ap-southeast-1.compute.amazonaws.com;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
 root /home/ec2-user/rails_demo/current/public/;
 #root /usr/share/nginx/html;
 #index index.html index.htm;
# First attempt to serve request as file, then
 # as directory, then fall back to index.html
 try_files $uri $uri/ /index.html;
 # Uncomment to enable naxsi on this location
 # include /etc/nginx/naxsi.rules
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Host $http_host;
 proxy_redirect off;
if (-f $request_filename/index.html) {
 rewrite (.*) $1/index.html break;
if (-f $request_filename.html) {
 rewrite (.*) $1.html break;
if (!-f $request_filename) {
 proxy_pass http://thin;
 } }
# redirect server error pages to the static page /40x.html
 error_page 404 public/404.html;
 location = /40x.html {
 root /usr/share/nginx/html;
# redirect server error pages to the static page /50x.html
 error_page 500 502 503 504 /50x.html;
 location = /50x.html {
 root /usr/share/nginx/html;
include /etc/nginx/conf.d/*.conf;

Save this file, and

sudo service nginx start