Migrating from Apache to nginx

Yesterday I changed out the web server software on this server, replacing the venerable Apache with a web server almost nobody’s ever heard of except for, ironically, Russian spammers.

Well, OK, more people than that have heard of nginx, a lightweight, high-performance HTTP server and reverse proxy which was written by Russian coder Igor Sysoev. nginx is supposed to be able to handle as many as 8,000 to 10,000 requests per second using comparatively little memory, and while I get nowhere near that level of traffic, Apache has been a bit of a dog for quite a while, eating up all my memory and on occasion just taking my server out entirely.

So it had to go.

Now if you’re a web server administrator and you’re thinking of replacing Apache (and if you aren’t, you should think about it) the first thing you have to know is that there is no drop-in replacement for Apache. Things you’ve been accustomed to for years are suddenly going to be entirely different. For instance, nginx has no facility comparable to Apache’s .htaccess files, so it’s not going to work for shared hosting providers where multiple users have web sites on a single server. But I don’t have this problem, since I run all my sites on my own CentOS-based servers.

The first thing I did was to get a copy of the nginx-0.6.31 source RPM from the Fedora repository, update it to the latest version (0.7.6 as of this writing) and rebuild RPMs on my CentOS box. RPM may not be the perfect packager, but it at least keeps track of everything, so I try to use RPMs to install software whenever possible. If they aren’t available in CentOS repositories, I’ll grab a Fedora RPM and rebuild it. (Update: I now use nginx from the remi repository, which is kept up to date. He also keeps PHP, php-fpm and MySQL up to date, saving me a lot of headaches.)

After spending several hours Sunday writing up configuration files for my 16 HTTP virtual hosts and two SSL hosts, adding in customized nginx rewrite rules for software such as WordPress that I run, writing my own Red Hat init script to start PHP in FastCGI mode, and testing as much as I could, I stopped Apache and started nginx around midnight. I had a few problems with an SMF forum that I have, and caught a problem where I put the wrong document root in one virtual host, but the cutover went largely without incident. (Update: Now that php-fpm is included in PHP, it’s no longer necessary to do any of this.)

I’d never used FastCGI before, and all my previous experiences with PHP in CGI mode were disappointing. I liked having it as an Apache module. And FastCGI doesn’t address my number one complaint about CGI, that it mangles HTTP headers. so I can’t tell in PHP exactly what came in on the wire. But I can’t argue with the results: With nginx, the site is much faster, memory usage has dropped significantly, the site is much faster, my server is running cooler, the site is much faster, and did I mention the site’s much faster?

I can probably even get away with downgrading my server and still have much more capacity to serve requests than I did with Apache. It’s that much smaller and faster.

I do have two complaints about missing features in nginx, though. The first is the lack of IPv6 support. I’m told that Igor plans to add this in the near future, and if he doesn’t, I may do it myself. (Update: nginx now fully supports IPv6.)

The second is the lack of content negotiation. This is where the server dynamically picks a file based on one or more possible alternatives. For instance if you request /index then the server looks for all files starting with /index and serves whichever one it thinks is most appropriate given whatever information the user has supplied (e.g. language, content encoding, and so forth). I used this little trick to remove the “.php” from some of my URLs in some custom PHP scripts, and all of those broke. Fortunately I was able to work around this with a simple configuration file change which I share here in case it helps someone else. Add this in the relevant location section:

                if (-e $request_filename.php) {
                        rewrite ^(.+)$ $1.php last;
                }

This will serve a file /example.php when you request the file /example.

Overall I’m quite impressed with nginx so far. I expect that now this server will stand up to anything that digg or slashdot can throw at it without even blinking. And now that I have room to grow, it’s time to actually start growing until I am getting 8,000 hits a second.