Lately I tried to install a complete Rails 3 nginx unicorn web stack on a shared host without root privileges, in my user home directory.
In this process, here are the software versions used:
- Ruby: 1.8.7 p370
- RubyGems: 1.6.2
- Rails: 3.2.12
- nginx: 1.2.7
- Unicorn: 4.5.0
For info, I tested this setup successfully on a PlanetHoster’s shared web host.
In this tutorial, the user account is named “myuser”, belonging to the group “mygroup”. The web server (nginx) will run as this user (non-root), therefore in on a port number >1024 (I chose 12006). The Rails3 application is named “myapp”, and is accessible using the external url “http://my_public_url.com”
Before beginning, it is important to make sure development tools (at least gcc
, ld
and make
) are available on the shared web host environment, and that you have an SSH access to your web host.
Here are the steps to get it running:
Ruby and Rails3 setup
- Setup your RubyGems config to install gems locally in your home directory (if not already done).
This is done first by setting a few environment variables:export GEM_PATH="/home/myuser/gems:/usr/lib/ruby/gems/1.8" export GEM_HOME="/home/myuser/gems" export PATH="/home/myuser/gems/bin:${PATH}"
This will tell RubyGems to install gems in the
/home/myuser/gems
directory, and to use Gems’ binaries from your local installation first.
Eventually create the/home/myuser/gems
directory.
Then edit RubyGems configuration file to set Gem files installation local (this will be useful to be used using bundler later):--- gem: --local --run-tests gemhome: /home/myuser/gems gempath: [] rdoc: --inline-source --line-numbers
- Install Rails3:
gem install rails
This will install all gems in your local
/home/myuser/gems
directory. - Get your Rails3 application code in a local directory, or create a new one using
rails new myapp
in a directory. In my tutorial, the app is in/home/myuser/rails_apps/myapp
. - Install all gems needed by your Rails3 application using bundler from your application directory (
/home/myuser/rails_apps/myapp
):bundle install
- Setup your Rails3 application: database connection, configuration…
- Test your application by running it using the default webrick server:
rails s -e production -p 12006
You should be able to see your Rails application running using top (in another terminal):
> top -u myuser -c top - 12:52:21 up 64 days, 9:37, 2 users, load average: 2.47, 2.54, 2.64 Tasks: 310 total, 2 running, 307 sleeping, 0 stopped, 1 zombie Cpu(s): 11.4%us, 2.7%sy, 0.6%ni, 77.8%id, 7.3%wa, 0.0%hi, 0.1%si, 0.0%st Mem: 10376292k total, 8649296k used, 1726996k free, 432028k buffers Swap: 5406712k total, 1220k used, 5405492k free, 5260176k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 26747 myuser 21 0 2596 1096 732 R 1.9 0.0 0:00.02 top -u myuser -c 25523 myuser 15 0 33212 26m 3872 S 0.0 0.3 0:01.69 /usr/bin/ruby script/rails s -e production -p 12006
You should also be able to see your Rails3 application running at your external URL, on port 12006 (for example
http://my_public_url.com:12006
).
Once this is checked, you can stop your Rails3 webrick server. We will now use nginx and Unicorn to make it running.
nginx setup
- Install nginx in a local directory, grabbing it first from nginx downloads (check nginx download page to get the latest version URL.
mkdir ~/nginx cd ~/nginx wget http://nginx.org/download/nginx-1.2.7.tar.gz tar xvzf nginx-1.2.7.tar.gz cd nginx-1.2.7 ./configure --prefix=/home/myuser/nginx/nginx-1.2.7-install --user=myuser --group=mygroup --with-http_ssl_module make make install
This has installed nginx in directory
/home/myuser/nginx/nginx-1.2.7-install
- Modify nginx configuration file for it to run on a port >1024.
keepalive_timeout 65; #gzip on; server { listen 12006; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main;
- Test nginx works correctly by running it from its installation directory
/home/myuser/nginx/nginx-1.2.7-install
:./sbin/nginx
Now you should see a master and worker processes running using top:
> top -u myuser -c top - 12:31:02 up 64 days, 9:16, 2 users, load average: 3.17, 2.80, 2.67 Tasks: 314 total, 4 running, 310 sleeping, 0 stopped, 0 zombie Cpu(s): 16.2%us, 22.0%sy, 0.0%ni, 39.0%id, 22.6%wa, 0.0%hi, 0.2%si, 0.0%st Mem: 10376292k total, 8240012k used, 2136280k free, 472592k buffers Swap: 5406712k total, 1220k used, 5405492k free, 4849432k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 5033 myuser 18 0 2600 1240 836 R 0.7 0.0 0:00.03 top -u myuser -c 12718 myuser 25 0 5860 700 336 S 0.0 0.0 0:00.00 nginx: master process ./sbin/nginx 12720 myuser 15 0 6084 1136 568 S 0.0 0.0 0:00.00 nginx: worker process
You can already access your server externally using its port (here: 12006): you should see nginx welcoming page (for example at
http://my_public_url.com:12006
).
Once this is checked, you can shutdown nginx server by issuing:./sbin/nginx -s stop
Unicorn setup
- Add the
unicorn
gem to your Rails application’s Gemfilesource 'https://rubygems.org' gem 'rails', '3.2.12' gem 'unicorn'
- Install the unicorn gem from your application’s directory:
bundle install
- Create the Unicorn configuration file in your Rails’ application
config/unicorn.rb
(adapt it to your needs in highlighted lines):# config/unicorn.rb # Set environment to development unless something else is specified env = ENV["RAILS_ENV"] || "development" # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete # documentation. worker_processes 4 # listen on both a Unix domain socket and a TCP port, # we use a shorter backlog for quicker failover when busy listen "/home/myuser/rails_apps/myapp/tmp/my_site.socket", :backlog => 64 # Preload our app for more speed preload_app true # nuke workers after 30 seconds instead of 60 seconds (the default) timeout 30 pid "/home/myuser/rails_apps/myapp/tmp/unicorn.my_site.pid" # Production specific settings if env == "production" # Help ensure your application will always spawn in the symlinked # "current" directory that Capistrano sets up. working_directory "/home/myuser/rails_apps/myapp" # feel free to point this anywhere accessible on the filesystem user 'myuser', 'mygroup' shared_path = "/home/myuser/rails_apps/myapp" stderr_path "#{shared_path}/log/unicorn.stderr.log" stdout_path "#{shared_path}/log/unicorn.stdout.log" end before_fork do |server, worker| # the following is highly recomended for Rails + "preload_app true" # as there's no need for the master process to hold a connection if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! end # Before forking, kill the master process that belongs to the .oldbin PID. # This enables 0 downtime deploys. old_pid = "/home/myuser/rails_apps/myapp/tmp/unicorn.my_site.pid.oldbin" if File.exists?(old_pid) && server.pid != old_pid begin Process.kill("QUIT", File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH # someone else did our job for us end end end after_fork do |server, worker| # the following is *required* for Rails + "preload_app true", if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection end # if preload_app is true, then you may also want to check and # restart any other shared sockets/descriptors such as Memcached, # and Redis. TokyoCabinet file handles are safe to reuse # between any number of forked children (assuming your kernel # correctly implements pread()/pwrite() system calls) end
- Check that your application runs correctly on Unicorn by starting it from your rails application directory:
unicorn_rails -c config/unicorn.rb -E production -D -p 12006
You should now see 5 new processes in your top: the unicorn master and 5 workers:
> top -u myuser -c top - 17:21:53 up 64 days, 14:06, 2 users, load average: 1.20, 1.19, 1.36 Tasks: 306 total, 2 running, 303 sleeping, 0 stopped, 1 zombie Cpu(s): 11.4%us, 2.7%sy, 0.6%ni, 77.8%id, 7.3%wa, 0.0%hi, 0.1%si, 0.0%st Mem: 10376292k total, 9823156k used, 553136k free, 419012k buffers Swap: 5406712k total, 1220k used, 5405492k free, 6433556k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 19318 myuser 18 0 2596 1160 760 R 1.9 0.0 0:00.01 top -u myuser -c 14518 myuser 15 0 39800 33m 3940 S 0.0 0.3 0:01.80 unicorn_rails master -c config/unicorn.rb -E production -D -p 12006 14564 myuser 18 0 39884 31m 2320 S 0.0 0.3 0:00.09 unicorn_rails worker[0] -c config/unicorn.rb -E production -D -p 12006 14565 myuser 15 0 39808 31m 2276 S 0.0 0.3 0:00.07 unicorn_rails worker[1] -c config/unicorn.rb -E production -D -p 12006 14566 myuser 15 0 39800 30m 1732 S 0.0 0.3 0:00.01 unicorn_rails worker[2] -c config/unicorn.rb -E production -D -p 12006 14567 myuser 15 0 39808 31m 2096 S 0.0 0.3 0:00.05 unicorn_rails worker[3] -c config/unicorn.rb -E production -D -p 12006
And now you should again be able to see your Rails application served by Unicorn using its external URL
http://my_public_url.com:12006
.
Then you can shut down your Unicorn server by using the PID of the master process (1234 in my example), and sending the QUIT signal:kill -QUIT 1234
Unicorn setup with nginx
Now that your Rails application can execute in Unicorn and you have a valid nginx setup, it is time to configure them to put Unicorn behind nginx.
You may have noticed in our Unicorn configuration that the Unicorn server also listens to a socket Unix file (/home/myuser/rails_apps/myapp/tmp/my_site.socket
). Time for nginx to route requests to this socket file.
- Update nginx configuration file to send requests to the Unix socket. This is done by adding the
upstream
section:#gzip on; # this can be any application server, not just Unicorn/Rainbows! upstream app_server { # fail_timeout=0 means we always retry an upstream even if it failed # to return a good HTTP response (in case the Unicorn master nukes a # single worker for timing out). # for UNIX domain socket setups: server unix:/home/myuser/rails_apps/myapp/tmp/my_site.socket fail_timeout=0; # for TCP setups, point these to your backend servers # server 192.168.0.7:8080 fail_timeout=0; # server 192.168.0.8:8080 fail_timeout=0; # server 192.168.0.9:8080 fail_timeout=0; } server { listen 12006;
- Update nginx configuration file to server static files from your Rails’ public directory. This is done by removing the / location and adding a
root
directive:server { listen 12006; server_name my_public_url.com; #charset koi8-r; #access_log logs/host.access.log main; # Comment out "location /" section #location / { # root html; # index index.html index.htm; #} # Serve static files from the Rails application root /home/myuser/rails_apps/myapp/public; #error_page 404 /404.html;
- Update nginx configuration file to server all URI to our application server. This is done by adding a
location
section handling all URIs:root /home/xaeoncom/rails_apps/testr3/public; # Prefer to serve static files directly from nginx to avoid unnecessary # data copies from the application server. # # try_files directive appeared in in nginx 0.7.27 and has stabilized # over time. Older versions of nginx (e.g. 0.6.x) requires # "if (!-f $request_filename)" which was less efficient: # http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127 try_files $uri/index.html $uri.html $uri @app; location @app { # an HTTP header important enough to have its own Wikipedia entry: # http://en.wikipedia.org/wiki/X-Forwarded-For proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # enable this if you forward HTTPS traffic to unicorn, # this helps Rack set the proper URL scheme for doing redirects: # proxy_set_header X-Forwarded-Proto $scheme; # pass the Host: header from the client right along so redirects # can be set properly within the Rack application proxy_set_header Host $http_host; # we don't want nginx trying to do something clever with # redirects, we set the Host: header above already. proxy_redirect off; # set "proxy_buffering off" *only* for Rainbows! when doing # Comet/long-poll/streaming. It's also safe to set if you're using # only serving fast clients with Unicorn + nginx, but not slow # clients. You normally want nginx to buffer responses to slow # clients, even with Rails 3.1 streaming because otherwise a slow # client can become a bottleneck of Unicorn. # # The Rack application may also set "X-Accel-Buffering (yes|no)" # in the response headers do disable/enable buffering on a # per-response basis. # proxy_buffering off; proxy_pass http://app_server; } #error_page 404 /404.html;
- Start your nginx server from its installation directory:
./sbin/nginx
- Start your Unicorn server from your Rails application directory, with no port specified (it will use the Unix socket from its configuration instead):
unicorn_rails -E production -D -c config/unicorn.rb
You can find a complete nginx.conf file example here.
And now you should be all set.
You should have master and worker processes for both nginx and unicorn:
> top -u myuser -c top - 20:16:39 up 64 days, 17:01, 3 users, load average: 1.45, 1.12, 1.15 Tasks: 295 total, 1 running, 293 sleeping, 0 stopped, 1 zombie Cpu(s): 12.8%us, 1.6%sy, 0.0%ni, 69.3%id, 16.1%wa, 0.0%hi, 0.2%si, 0.0%st Mem: 10376292k total, 10123176k used, 253116k free, 439456k buffers Swap: 5406712k total, 1220k used, 5405492k free, 7523664k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 30744 myuser 15 0 2556 1184 808 R 0.3 0.0 0:14.72 top -u myuser -c 631 myuser 15 0 39788 33m 3892 S 0.0 0.3 0:01.67 unicorn_rails master -E production -D -c config/unicorn.rb 660 myuser 15 0 39788 30m 1760 S 0.0 0.3 0:00.01 unicorn_rails worker[0] -E production -D -c config/unicorn.rb 661 myuser 15 0 39788 30m 1760 S 0.0 0.3 0:00.01 unicorn_rails worker[1] -E production -D -c config/unicorn.rb 662 myuser 18 0 39868 31m 2332 S 0.0 0.3 0:00.05 unicorn_rails worker[2] -E production -D -c config/unicorn.rb 663 myuser 15 0 39868 31m 2336 S 0.0 0.3 0:00.07 unicorn_rails worker[3] -E production -D -c config/unicorn.rb 8612 myuser 25 0 6000 704 336 S 0.0 0.0 0:00.00 nginx: master process ./sbin/nginx 8615 myuser 15 0 6152 1196 620 S 0.0 0.0 0:00.00 nginx: worker process
And your Rails application should be accessible using its external URL: http://my_public_url.com:12006
, running nginx, forwarding requests to unicorn.