Experiments with Nginx

Lighter, smaller, faster, better? Nginx in action

There's no doubt that the Web runs on Apache. According to NetCraft, 47.1 percent of all Web sites are running on the crown jewel of the open source movement. Microsoft's IIS is a distant second at 24.8 percent, down a whopping 3.6 percent from last month. At third is qq.com, thanks to China, Google's fourth, and Nginx is fifth, running 3.7 percent of all Web sites. Nginx hasn't even been around for more than a few years, and it has only recently garnered any real interest. You'll be hearing much more about it, however. Nginx (or Engine-X) is a very impressive piece of code, currently running large sites like wordpress.com.

The whole impetus of Nginx is to be lighter, faster, and less resource intensive than Apache. To do this, it necessarily jettisons lots of functionality in favor of raw speed. For instance, Nginx can't handle CGI requests itself, so it proxies to another handler, like spawn-fcgi. But that's the idea -- you only need to run exactly what you need to run, without spending time and effort to pare Apache down to the bare essentials. In accordance to that design, a default Nginx installation has a very minimalist configuration that works fantastically for static Web tasks such as static HTML, style sheets, images, and so forth. For large sites that have dedicated image servers, it's not outside the realm of possibility that you could double the RPS from that server simply by moving to Nginx from Apache.

[ Stay up to date on the latest open source developments with InfoWorld's Technology: Open Source newsletter. ]

But what if you do need to push Nginx past simple static serving, into more advanced areas like URL rewriting, CGI tasks, and so forth? It can be done, though sometimes it gets, well, interesting. To start, let's look at a baseline Nginx configuration for a fairly beefy box:

user www www;

worker_processes 2; pid /var/run/nginx.pid; # [ debug | info | notice | warn | error | crit ] error_log /var/log/nginx.error_log info; events {    worker_connections 2000;    # use [ kqueue | rtsig | epoll | /dev/poll | select | poll ] ;   use kqueue; }

This sets the user to run Nginx as, the error logs, the number of Nginx processes, and worker connections (note that I'm using kqueue here since this is a FreeBSD box). Pretty simple. Now, let's tweak some other settings:

http { include mime.types; default_type application/octet-stream; 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; tcp_nodelay on; send_lowat 12000; keepalive_timeout 25;

gzip on; gzip_min_length 1100; gzip_buffers 4 8k; gzip_types text/plain;

Now we're set our logfile format, tweaked some TCP performance parameters, configured HTTP keepalive timeouts, and enabled gzip compression for compatible browsers. That's the basics. Now, maybe we want to stretch things out. For one thing, perhaps we need to run PHP code through Nginx, using spawn-fcgi like so:

/usr/local/bin/spawn-fcgi -f /usr/local/bin/php-cgi -a -p 51115 -P


This will run a listener for php-cgi on localhost, port 51115. Then, you instruct Nginx to pass all PHP tasks to the php-cgi listener:

 location ~ \.php$ { 
      fastcgi_pass   localhost:51115;  
      fastcgi_index  index.php; 
      fastcgi_param  SCRIPT_FILENAME /usr/local/www$fastcgi_script_name;   
      include        fastcgi_params; 

Thus, Nginx passes the request to php-cgi, and passes the result to the requesting browser. Voilà. The include is simply a translation file that maps Nginx parameters to CGI parameters, such as REQUEST_URI and QUERY_STRING.

So now we have a functioning Nginx server. Let's define a host:

server { 
     listen       80; 
     server_name  www.mydomain.com mydomain.com;
     access_log  /var/log/httpd/www-nginx-access.log  main; 
     location ~ /\.   {
           deny  all;
     location ~* ^.+.(inc|tpl|h|ihtml|sql|ini|conf|class|pl)$ {
           deny  all;

location / {
     root   /usr/local/www; 
     index  index.html index.php index.htm;

So we've configured a virtual host, and blocked a bunch of files from public view that should be hidden. But what if we need to do some complex URL rewrites, à la Apache's mod_rewrite?

location / {
      rewrite ^/olddir/(.*)$ /newdir/$1 permanent;

Done and done. Things can get a bit wonky when you push much further than that, like trying to use multiple rewrite conditions to determine if a rewrite should occur. It's possible to do this since you can set variable in the configuration, so you could potentially write a series of if statements that could collectively determine whether or not the URL should be rewritten, and then perform the action at the end. This isn't as resource-intensive as it might sound, because you can explicitly break rewrite flows if a condition is not met early, for instance. I'd suggest the Nginx Forums for a much deeper discussion into this and other Nginx concepts, like the use of Nginx as a reverse HTTP proxy/cache, load balancer, mail proxy, and other features.

I've been pretty happy running Nginx on a few relatively high-performance sites, with the worst requiring a few hours to replicate the necessary portions of the Apache configuration to Nginx. Because the document roots and all assets didn't move, it's simple to switch between Nginx and Apache at a whim. Even for highly dynamic sites, the use of Nginx and opcode caches can take a smaller server quite a long way -- and take a big server even further. You can grab the source at nginx.net, though the docs on that site are in Russian. I've been impressed so far, and so might you be.

After all, there's certainly a reason that Nginx has increased its presence by 1 percent in past month, and there's never a bad time to squeeze more performance out of your Web servers.

Copyright © 2009 IDG Communications, Inc.