# download and build nginx from sources
https://gist.github.com/3083579
# nginx upstart config
https://gist.github.com/2024203
# nginx.conf
user nginx;
worker_processes 8;
worker_priority -5;
error_log logs/nginx.error.log crit;
events {
use epoll;
worker_connections 8192;
}
timer_resolution 500ms;
http {
include mime.types;
default_type application/octet-stream;
server_name_in_redirect off;
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
sendfile on;
tcp_nopush on;
keepalive_timeout 10;
tcp_nodelay on;
client_max_body_size 1m;
client_body_buffer_size 128k;
client_body_timeout 30;
client_body_temp_path /tmp/client_body_temp;
client_header_timeout 30;
client_header_buffer_size 128;
gzip on;
gzip_http_version 1.1;
gzip_disable "msie6";
gzip_vary on;
gzip_min_length 1100;
gzip_buffers 64 8k;
gzip_comp_level 3;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
ssl_certificate /etc/nginx/ssl_certs/server.crt;
ssl_certificate_key /etc/nginx/ssl_certs/server.key;
ssl_session_timeout 15m;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
add_header Strict-Transport-Security "max-age=16070400; includeSubdomains";
add_header X-Frame-Options DENY;
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
include /etc/nginx/sites-enabled/*;
}
# Nginx server block configuration with proxy_pass to Unicorn upstream
# We use full-SSL site with web-server redirection, no mess with Rails application redirection
#
# config/server/production/nginx_host.conf
upstream unicorn {
server unix:/tmp/unicorn.production.sock fail_timeout=0;
}
server {
listen 80;
server_name server.com;
rewrite ^(.*) https://$host$1 permanent;
location ~ \.(php|html)$ {
deny all;
}
access_log /dev/null;
error_log /dev/null;
}
server {
ssl on;
listen 443 ssl;
server_name server.com;
root /home/app/public_html/app_production/current/public;
try_files $uri /system/maintenance.html @unicorn;
location @unicorn {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://unicorn;
limit_req zone=one;
access_log /dev/null;
error_log logs/unicorn.error.log;
}
location ~ ^/(assets|images|javascripts|stylesheets|swfs|system)/ {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}
include /home/app/public_html/app_production/current/config/server/production/nginx_errors.conf;
access_log /dev/null;
error_log /dev/null;
}
# nginx configuration piece to handle errorrs
#
# config/server/production/nginx_errors.conf
error_page 500 502 504 /500.html;
error_page 503 @503;
location = /50x.html {
root html;
}
location = /404.html {
root html;
}
location @503 {
error_page 405 = /system/maintenance.html;
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html break;
}
rewrite ^(.*)$ /503.html break;
}
if ($request_method !~ ^(GET|HEAD|PUT|POST|DELETE|OPTIONS)$ ){
return 405;
}
if (-f $document_root/system/maintenance.html) {
return 503;
}
location ~ \.(php|html)$ {
return 405;
}
# Capistrano configuration
#
# require 'new_relic/recipes' - Newrelic notification about deployment
# require 'capistrano/ext/multistage' - We use 2 deployment environment: staging and production.
# set :deploy_via, :remote_cache - fetch only latest changes during deployment
# set :normalize_asset_timestamps - no need to touch (date modification) every assets
# "deploy:web:disable" - traditional maintenance page (during DB migrations deployment)
# task :restart - Unicorn with preload_app should be reloaded by USR2+QUIT signals, not HUP
#
# http://unicorn.bogomips.org/SIGNALS.html
# "If “preload_app” is true, then application code changes will have no effect;
# USR2 + QUIT (see below) must be used to load newer code in this case"
#
# config/deploy.rb
require 'bundler/capistrano'
require 'capistrano/ext/multistage'
require 'new_relic/recipes'
set :stages, %w(staging production)
set :default_stage, "staging"
set :scm, :git
set :repository, "..."
set :deploy_via, :remote_cache
default_run_options[:pty] = true
set :application, "app"
set :use_sudo, false
set :user, "app"
set :normalize_asset_timestamps, false
# Optional
before "deploy", "deploy:web:disable"
before "deploy:stop", "deploy:web:disable"
after "deploy:update_code", "deploy:symlink_shared"
# Optional
after "deploy:start", "deploy:web:enable"
after "deploy", "deploy:web:enable"
after "deploy", "deploy:cleanup"
namespace :deploy do
%w[start stop].each do |command|
desc "#{command} unicorn server"
task command, :roles => :app, :except => { :no_release => true } do
run "#{current_path}/config/server/#{rails_env}/unicorn_init.sh #{command}"
end
end
desc "restart unicorn server"
task :restart, :roles => :app, :except => { :no_release => true } do
run "#{current_path}/config/server/#{rails_env}/unicorn_init.sh upgrade"
end
desc "Link in the production database.yml and assets"
task :symlink_shared do
run "ln -nfs #{deploy_to}/shared/config/database.yml #{release_path}/config/database.yml"
end
namespace :web do
desc "Maintenance start"
task :disable, :roles => :web do
on_rollback { run "rm #{shared_path}/system/maintenance.html" }
page = File.read("public/503.html")
put page, "#{shared_path}/system/maintenance.html", :mode => 0644
end
desc "Maintenance stop"
task :enable, :roles => :web do
run "rm #{shared_path}/system/maintenance.html"
end
end
end
namespace :log do
desc "A pinch of tail"
task :tailf, :roles => :app do
run "tail -n 10000 -f #{shared_path}/log/#{rails_env}.log" do |channel, stream, data|
puts "#{data}"
break if stream == :err
end
end
end
# capistrano production config
#
# config/deploy/production.rb
server "8.8.8.8", :app, :web, :db, :primary => true
set :branch, "production"
set :deploy_to, "/home/app/public_html/app_production"
set :rails_env, "production"
# Unicorn configuration file to be running by unicorn_init.sh with capistrano task
# read an example configuration before: http://unicorn.bogomips.org/examples/unicorn.conf.rb
#
# working_directory, pid, paths - internal Unicorn variables must to setup
# worker_process 4 - is good enough for serve small production application
# timeout 30 - time limit when unresponded workers to restart
# preload_app true - the most interesting option that confuse a lot of us,
# just setup is as true always, it means extra work on
# deployment scripts to make it correctly
# BUNDLE_GEMFILE - make Gemfile accessible with new master
# before_fork, after_fork - reconnect to all dependent services: DB, Redis, Sphinx etc.
# deal with old_pid only if CPU or RAM are limited enough
#
# config/server/production/unicorn.rb
app_path = "/home/app/public_html/app_production/current"
working_directory "#{app_path}"
pid "#{app_path}/tmp/pids/unicorn.pid"
stderr_path "#{app_path}/log/unicorn.log"
stdout_path "#{app_path}/log/unicorn.log"
listen "/tmp/unicorn.production.sock"
worker_processes 4
timeout 30
preload_app true
before_exec do |server|
ENV["BUNDLE_GEMFILE"] = "#{app_path}/Gemfile"
end
before_fork do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
if defined?(Resque)
Resque.redis.quit
end
sleep 1
end
after_fork do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
if defined?(Resque)
Resque.redis = 'localhost:6379'
end
end
# Unicorn handle shell script
#
# APP_ROOT, PID - are the same as you setup above
# CMD - use bundle binstubs (bundle install --binstubs) to
# forget about "bundle exec" stuff, run in demonize mode
# bin/unicorn is for Rack application (config.ru in root dir), but
# bin/unicorn_rails is to use with Rails 2.3
#
# To handle "app_preload true" configuration we should use USR2+QUIT signals, not HUP!
# So we rewrite capistrano deployment scripts to manage it.
#
# config/server/production/unicorn_init.sh
#!/bin/sh
set -e
# Example init script, this can be used with nginx, too,
# since nginx and unicorn accept the same signals
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/app/public_html/app_production/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/server/unicorn.rb -E production"
action="$1"
set -u
old_pid="$PID.oldbin"
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $old_pid && kill -$1 `cat $old_pid`
}
case $action in
start)
sig 0 && echo >&2 "Already running" && exit 0
$CMD
;;
stop)
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload)
sig HUP && echo reloaded OK && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
$CMD
;;
upgrade)
if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
then
n=$TIMEOUT
while test -s $old_pid && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && test -s $old_pid
then
echo >&2 "$old_pid still exists after $TIMEOUT seconds"
exit 1
fi
exit 0
fi
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
$CMD
;;
reopen-logs)
sig USR1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
exit 1
;;
esac
# deploy speed up via SSH ControlMaster
#
$ mkdir ~/.ssh/cm_socket
$ vim ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/cm_socket/%r@%h:%p
$ cd ~/app
$ cap deploy