Why I switched to nginx

When I originally setup my VPS I didn’t think twice about using Apache for my web serving duties. Setting up the LAMP environment was very easy and everything that I needed to host worked perfectly with this setup. However, when I decided to create my blog using Rails, I knew I would have to modify my setup. Originally I had planned to use mod_proxy_balancer and mongrel, but then Phusion’s Passenger came out and that was easy enough to get running. However, I was itching to try something new, improve the speed of my sites and lower the amount of resources that were being used on my Slice. Enter Nginx.

Here’s why I decided to give Nginx a shot:

  • Nginx is lightweight and has a small memory footprint
  • Nginx is super fast
  • Nginx can be easily configured to send rails requests to mongrel, thin and others
  • Nginx has easy to read configuration logs and rewriting rules
  • I get to learn something new!

Note: All ApacheBench test results displayed are the from the 4th consecutive test. This minimizes the negative effects of the slow startup of ruby processes with Phusion Passenger where the initial requests take a considerably long time.

Apache vs. Nginx Memory Usage

For this test I ran ApacheBench against the index page of a well trafficked PunBB forum that I host on my slice. I fired up top and found the following:

Apache

Mem: 262316k total, 207140k used, 55176k free, 4280k buffers

Nginx

Mem: 262316k total, 146956k used, 115360k free, 3844k buffers

That’s about 40MB less with Nginx. That makes a big difference when you’re only starting out with 256MB.

Apache vs. Nginx AB results

Apache

This is ApacheBench, Version 2.0.41-dev <$Revision: 1.141 $> apache-2.0
		Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
		Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
		Benchmarking www.example.com (be patient).....done
 
 
		Server Software:        Apache
		Server Hostname:        www.example.com
		Server Port:            80
 
		Document Path:          /
		Document Length:        8857 bytes
 
		Concurrency Level:      10
		Time taken for tests:   1.732965 seconds
		Complete requests:      100
		Failed requests:        0
		Write errors:           0
		Total transferred:      928532 bytes
		HTML transferred:       897838 bytes
		Requests per second:    57.70 [#/sec] (mean)
		Time per request:       173.296 [ms] (mean)
		Time per request:       17.330 [ms] (mean, across all concurrent requests)
		Transfer rate:          522.80 [Kbytes/sec] received
 
		Connection Times (ms)
		              min  mean[+/-sd] median   max
		Connect:       22   26   4.3     26      40
		Processing:    70  136  41.5    133     268
		Waiting:       46  105  40.9    100     239
		Total:         93  163  42.4    160     294
 
		Percentage of the requests served within a certain time (ms)
		  50%    160
		  66%    174
		  75%    186
		  80%    197
		  90%    213
		  95%    250
		  98%    274
		  99%    294
		 100%    294 (longest request)

Nginx

	This is ApacheBench, Version 2.0.41-dev <$Revision: 1.141 $> apache-2.0
	Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
	Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
	Benchmarking www.example.com (be patient).....done
 
 
	Server Software:        Nginx/0.6.32
	Server Hostname:        www.example.com
	Server Port:            8080
 
	Document Path:          /
	Document Length:        8858 bytes
 
	Concurrency Level:      10
	Time taken for tests:   1.107068 seconds
	Complete requests:      100
	Failed requests:        0
	Write errors:           0
	Total transferred:      929232 bytes
	HTML transferred:       897920 bytes
	Requests per second:    90.33 [#/sec] (mean)
	Time per request:       110.707 [ms] (mean)
	Time per request:       11.071 [ms] (mean, across all concurrent requests)
	Transfer rate:          819.28 [Kbytes/sec] received
 
	Connection Times (ms)
	              min  mean[+/-sd] median   max
	Connect:       22   25   4.3     25      42
	Processing:    62   80  29.4     75     345
	Waiting:       37   49  10.6     47      93
	Total:         85  106  29.5    100     368
 
	Percentage of the requests served within a certain time (ms)
	  50%    100
	  66%    105
	  75%    109
	  80%    112
	  90%    119
	  95%    146
	  98%    147
	  99%    368
	 100%    368 (longest request)

There are a few things that stand out to me from these tests:

  1. Requests Per Second: 90 vs 58, Nginx wins
  2. Mean Processing Time: 80 vs 136, Nginx wins
  3. Percentage of Requess served within 100ms: 50% vs. ??, Nginx Wins

Nginx and Thin vs. Apache and Phusion Passenger

So the PHP/MySQL stuff was all fine and good and that’s something that Apache and Nginx do very well, so no real surprises there. What about handling requests to a simple rails application like this blog?

For this test I again ran apache bench against the non-cached version of my root url. On this page there are some partials being rendered, active record database calls, images loading from Amazon’s S3 and some static files being served.

Apache 2.2 + Phusion Passenger 2.0.3

	This is ApacheBench, Version 2.0.41-dev <$Revision: 1.141 $> apache-2.0
	Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
	Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
	Benchmarking www.lengelzigich.com (be patient).....done
 
 
	Server Software:        Apache
	Server Hostname:        www.lengelzigich.com
	Server Port:            80
 
	Document Path:          /
	Document Length:        14894 bytes
 
	Concurrency Level:      10
	Time taken for tests:   26.118296 seconds
	Complete requests:      100
	Failed requests:        0
	Write errors:           0
	Total transferred:      1544644 bytes
	HTML transferred:       1493235 bytes
	Requests per second:    3.83 [#/sec] (mean)
	Time per request:       2611.830 [ms] (mean)
	Time per request:       261.183 [ms] (mean, across all concurrent requests)
	Transfer rate:          57.74 [Kbytes/sec] received
 
	Connection Times (ms)
	              min  mean[+/-sd] median   max
	Connect:       22   24   1.5     25      27
	Processing:   769 2477 389.8   2524    3076
	Waiting:      744 2448 387.8   2498    3048
	Total:        792 2501 390.1   2547    3102
 
	Percentage of the requests served within a certain time (ms)
	  50%   2547
	  66%   2637
	  75%   2793
	  80%   2819
	  90%   2906
	  95%   2963
	  98%   3079
	  99%   3102
	 100%   3102 (longest request)

Nginx + Thin

I decided to use thin instead of mongrel because, well, I guess I just wanted to try something new. I heard that thin had recently hit version 1.0 and figured it was worth checking out.

This is ApacheBench, Version 2.0.41-dev <$Revision: 1.141 $> apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
Benchmarking www.lengelzigich.com (be patient).....done
 
 
Server Software:        Nginx/0.6.32
Server Hostname:        www.lengelzigich.com
Server Port:            80
 
Document Path:          /
Document Length:        14894 bytes
 
Concurrency Level:      10
Time taken for tests:   17.334487 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      1534900 bytes
HTML transferred:       1489400 bytes
Requests per second:    5.77 [#/sec] (mean)
Time per request:       1733.449 [ms] (mean)
Time per request:       173.345 [ms] (mean, across all concurrent requests)
Transfer rate:          86.42 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       22   24   1.4     25      26
Processing:   526 1663 489.6   1544    2995
Waiting:      477 1613 491.6   1496    2949
Total:        549 1687 489.9   1567    3020
 
Percentage of the requests served within a certain time (ms)
  50%   1567
  66%   1882
  75%   1947
  80%   2034
  90%   2394
  95%   2648
  98%   3020
  99%   3020
 100%   3020 (longest request)

The results here are a little more impressive.

  1. Requests Per Second: 6 vs. 4, not great, but Nginx wins
  2. Mean Processing time: 1660 vs. 2448, Nginx wins
  3. Percentage of Requess served within 1560ms: 50% vs. ??, Nginx Wins

I don’t have the top memory output for these two tests handy, but with Apache free memory was hovering around 3MB. Nginx had free memory hovering around 12MB.

The key difference between Nginx and Apache is that with Apache there is a noticeable lag while Ruby processes are spun up by Passenger on the initial request, however, as I mentioned above, this is not reflected in these ApacheBench tests.

Plus, I Learned A Lot!

I think the best part of all of this was the overall learning experience. I broke out of my Apache shell and learned about a new web server, here are the highlights:

  • Learned how to install and configure Nginx, php-cgi, spawn-fcgi and thin on Ubuntu
  • Wrote new Capistrano deployment recipes to handle deploying to my new Nginx + thin setup
  • Created some new shell scripts to mimic common tasks from apache2ctl and a2ensite/a2dissite
  • Recreated my existing Apache virtual host setup with Nginx

If you’re currently running LAMP or using Apache and “mod rails”, give Nginx a shot. It’s easy to setup, doesn’t use too many resources, and best of all, it’s fast.

PHP Deployment With Capistrano

If you’ve ever used Capistrano to deploy your Rails application you know how simple the process is. With a little configuration you have a respectable deployment strategy that integrates nicely with your source control system and deployment server. If you’re stuck writing your apps in PHP you might think that you are restricted to using rsync, scp or plain old FTP. Fear not! Getting Capistrano deploying your PHP apps is a lot easier than you might think.

Assumptions & Requirements

There are assumptions that I will be making throughout this article, they include:

  • You are using OS X/Linux
  • You are comfortable using the command line
  • The code for your PHP application resides in some type of source control system
  • The deployment server is running some type of *nix
  • The deployment server is using a LAMP setup
  • You have Ruby and Ruby Gems installed on your system

Installing Capistrano

I’m using the latest (as of this post) version which is 2.4.3, it’s a typical gem install with:

	gem install -y Capistrano
	...
	Successfully installed Capistrano-2.4.3
	1 gem installed
	Installing ri documentation for Capistrano-2.4.3...
	Installing RDoc documentation for Capistrano-2.4.3...

Once you’ve got Capistrano installed we can move on to configuring our application.

Application Configuration

For this example I will be deploying a simple Hello World Application that I wrote in PHP. The code is stored in my subversion repository and I am deploying to my SliceHost Slice running Ubuntu Linux.

Step 1 – Create config Directory and Capify

Capistrano places its configuration file, called a recipe, in a directory named config. Since my application doesn’t have a config directory, I will create one.

clayton$ mkdir config

Next we will “Capify” the application. This creates the necessary default files.

	clayton$ capify .
	[add] writing `./Capfile'
	[add] writing `./config/deploy.rb'
	[done] capified!

We’ve got our default recipe in place and our application has been capified!

Step 2 – Modifying The Default Recipe (Configuration)

The default recipe is pretty simple and well(enough) documented. However, we do need to make some changes.

	# Application Naming
	#
	set :application, "set your application name here"
	set :repository,  "set your repository location here"

*:application: This is the name of the directory that will be created to hold the files related to your application. I will name mine “app”. It’s important to note that this probably *should not be your website’s DocumentRoot such as httpdocs or www.

*:repository*: This is the location of your source control repository. I’m using subversion so I’ll enter the URL for my repository.

	# Application Deployment Location
	#
	# If you aren't deploying to /u/apps/#{application} on the target
	# servers (which is the default), you can specify the actual location
	# via the :deploy_to variable:
	set :deploy_to, "/var/www/vhosts/hellowworld.tld/#{application}"
	set :document_root, "/var/www/vhosts/hellowworld.tld/httpdocs/current"

This bit of commented code is important. Chances are this is not how your server is configured and you will need to change the :deploy_to variable. You should set this to something reasonable, on my SliceHost server all of my sites are stored in /var/www/vhosts/_domain_/ so I’ll set my :deploy_to to something like /var/www/vhosts/hellowworld.tld.

*:document_root*: This isn’t a built in Capistrano variable, but I like to use to so that I can reference the DocumentRoot from my Apache VirtualHost later on in the deployment process.

Note: The previously set :application variable is appended to the end of your :deploy_to path so don’t put your application name in the :deploy_to variable.

	# Source Control Settings
	#
	set :scm_username, "cap" #if http
	set :scm_password, "fHw1n71d" #if http
	set :scm_checkout, "export"

If you’re using http to access your subversion repository you’ll need to add the username and password for a user who has httpauth access.

*:scm_username*: Provide the username of a user who has access to your subversion repository.

*:scm_password*: Provide the above user’s e-mail address.

*:scm_checkout*: Tell Capistrano what type of checkout to preform, we’ll use export.

Capistrano uses SSH to access your deployment server and run the commands required to deploy your application. You’ll need to tell Capistrano how to access your server.

	# SSH Settings
	#
	set :user, "clz"
	set :password, "password"
	set :use_sudo, false
	set :ssh_options, {:forward_agent => true}

*:user*: Set this to a user who has access to the :deploy_to directory.

*:password*: The above user’s password

*:use_sudo*: If the above user has sudo priviledges you can set this to true. I do not use it because of the way my server is configured. You might need to.

*:ssh_options*: This variable accepts a ruby hash, for valid options see Net-SSH. I’m using agent forwarding because I want to use my SSH key and don’t want to be prompted for a password. For more on agent forwarding see UnixWiz’s Illustrated Guide to SSH Agent Forwarding.

	# Server Roles
	#
	role :app, "your app-server here"
	role :web, "your web-server here"
	role :db,  "your db-server here", :primary => true

When deploying a Rails application it’s possible that you’ll have more than one application server and a database on another server, but with PHP chances are you’re running a LAMP setup and have Apache, PHP and MySQL all side-by-side. In my case I’m going to set all of these values to my server’s IP address.

Step 3 – Configuring For a PHP Deployment

If you were to try and deploy your application right now you’d run into a number of errors. Remember that Capistrano assumes you’re deploying a Rails application and because of that it will try and do some things that you don’t want it to do, restarting servers, creating Rails specific symbolic links etc. To get around these assumptions we will need to bypass or modify some of the existing default tasks.

	namespace :deploy do
 
		task :update do
			transaction do
				update_code
				symlink
			end
		end
 
		task :finalize_update do
			transaction do
				run "chmod -R g+w #{releases_path}/#{release_name}"
			end
		end
 
		task :symlink do
			transaction do
				run "ln -nfs #{current_release} #{deploy_to}/#{current_dir}"
				run "ln -nfs #{deploy_to}/#{current_dir} #{document_root}"
			end
		end
 
		task :migrate do
			# nothing
		end
 
		task :restart do
			# nothing
		end
 
	end

Update Task: This is a built in task in the deploy namespace. This is setup to do a few things, among them checkout your code from the repository and restart your server. However, we don’t need the server restart so we’ll override it and have it update the code and create symbolic links

Finalize Update: This was a step that I kept getting stuck on when first trying this out. I did not have this task in my deployment namespace and my file permissions were all goof’ed up. I found this via the Capistrano GitHub recipe.rb page.

Symlink: Since we’re not deploying directly to our site’s DocumentRoot we will need to tell Capistrano to create some symbolic links. When Capistrano updates the code, it checks out the lastest version of the code from your source control system and sticks it into a directory under deploy_to/releases inside of a directory named with the current timestamp. Usually Capistrano will create the a symbolic link named current that is pointing to the most recent release in deploy_to/releases, however, we’ll need to create that on our own.

*#{current_release}*: This is a default Capistrano variable for the most recent directory in deploy_to/releases.

*#{current_dir}*: This is a default Capistrano variable for the name of the “current” directory, by default it is current.

Migrate: Migrations are a Rails only thing so we’ll skip them and tell Capistrano to do nothing.

Restart: Capistrano will try to restart your Rails process or mongrels etc. Needless to say, with PHP this isn’t required. Technically you could throw a call to apache2ctl if you needed to.

Transactions: Recent versions of Capistrano allow you to specify transactions. If you encounter any errors while inside of a transaction all of the changes will be rolled back and your application won’t sit there on the internet broken.

Step 4 – The Initial “Setup” Deploy

The first step of deploying your PHP application with Capistrano is to run the setup task. This will create some directories on your deployment server in the correct location.

	clayton$ cap deploy:setup

Here’s what I got on my deployment server:

	/var/www/vhosts/helloworld.tld/app/shared
		/var/www/vhosts/helloworld.tld/app/shared/log
		/var/www/vhosts/helloworld.tld/app/shared/tmp
		/var/www/vhosts/helloworld.tld/app/shared/pid
	/var/www/vhosts/helloworld.tld/app/releases

You can get rid of the log, tmp and pid directories. Those are Rails specific and we won’t be using them.

Your application is now all ready to be deployed by Capistrano. You’ve even got a place to store some of your non-code related things like images and other files.

Step 5 – Making Use of Capistrano’s Shared Directories

My Hello World application has some images that are displayed on the page. However, I’m not storing my images in my source control system and thus, they are not automatically uploaded when I deploy my app with Capistrano. Luckily we can make use of Capistrano’s ability to run various commands on the deployment server to create some symbolic links to our images.

First, upload your images to a directory (call it whatever you want) under deploy_to/_application_/shared/. Mine is located at /var/www/vhosts/helloworld.tld/app/shared/images. Now we can update our recipe to create these symbolic links.

 
	# namespace :deploy... is up above
 
	task :after_symlink do
		transaction do
			run "ln -nsf #{shared_path}/images #{document_root}/images"
		end
	end

After Symlink: We want Capistrano to create our symbolic links after it has already linked up the document root and current release directories. Using run we tell Capistrano to execute the command on the deployment server. In this case we want to create a symbolic link in our document root that points to the shared images path.

*:shared_path_*: This is the default location and name of the “shared” directory created by our previous setup command.

By using symbolic links to link up our static non-code-related images, we avoid having to re-upload the images each time we deploy or worry about accidentally wiping out any files.

Step 6 – Deploying our PHP Application For the First Time

Typically with a Rails application you would run the following to deploy your application for the first time.

	clayton$ cap deploy:cold

This would checkout the code, rune the migrations and start your server. Since we don’t need those last two steps, we can just skip deploying “cold” and use the regular old deploy task.

	clayton$ cap deploy

At this point you’ll see a bunch of information running across your terminal window. If there are any errors you will see an error message from capistrano about a failed command on the deployment server or bad password. If everything went well your application will now be deployed on your server!

Future Deployments

Now that I’ve made some changes to my HelloWorld application I’m ready to deploy again. Capistrano makes this very easy, as long as I follow a simple process.

  1. Commit my changes to my source control system
  2. Upload any new static content (images, files etc.) to the shared directory
  3. Run cap deploy from my command line

You’ll again see the progress of Capistrano deploying the updated application run accross your terminal window and your new code will be deployed.

There’s A Lot to Learn About Capistrano

Capistrano is a great application, one of the best things about it is its simlicity and easy of use. However, it can also be pretty complex when you start digging into how it’s working behind the scenes. With that said, I encourage you to do some digging of your own and learn more about the default variables and tasks. There is probably quite a bit I could do above and beyond the defaults to make this PHP deployment strategy much better, but for now it works and my PHP deployments are easier than ever.

To learn more about Capistrano’s deployment recipes read the code and comments.

Final Deploy Recipe

View the full example deployment recipe