My more complex Web sites run atop WordPress on Apache and MySQL. Every so often, Apache devours all available memory and the server becomes very very slow. I must log in, kill Apache, and restart it. The more moving parts something has, the harder it is to debug. Apache, with all its modules, has a lot of moving parts.
After six months of intermittent debugging, I decided that with the new hardware I would switch Web server software, and settled on nginx. I’d like to switch to Postgres as well, but WordPress’s official release doesn’t yet support Postgres. WordPress seems to be the best of the available evils — er, Web site design tools. The new server runs FreeBSD 9/i386 running on VMWare ESXi. According to the documentations I’ve dug up, it should all Just Work.
Before making this kind of switch, check the nginx module comparison page. Look for the Apache modules you use, and see if they have an nginx equivalent. I know that nginx doesn’t use .htaccess for password protection; I must put my password protection rules directly in the nginx configuration. Also, nginx doesn’t support anything like the mod_security application firewall. I’ll have to find another way to deal with referrer spam, but at least the site will be up more consistently.
To start, I’m moving my static Web sites to the new server. (I’ll cover the WordPress parts in later posts.) I expect to get all of the functionality out of nginx that I have on Apache.
For many years, blackhelicopters.org was my main Web site. It’s now demoted to test status. Here’s the Apache 2.2 configuration for it.
<VirtualHost *:80>
ServerAdmin webmaster@blackhelicopters.org
DocumentRoot /usr/local/www/data/bh
ServerName blackhelicopters.org
ServerAlias www.blackhelicopters.org
ErrorDocument 404 /index.html
ErrorLog "|/usr/local/sbin/rotatelogs /var/log/bh/bh_error_log.%Y-%m-%d-%H_%M_%S 86400 -300"
CustomLog "|/usr/local/sbin/rotatelogs /var/log/bh/bh_spam_log.%Y-%m-%d-%H_%M_%S 86400 -300" combined env=spam
CustomLog "|/usr/local/sbin/rotatelogs /var/log/bh/bh_access_log.%Y-%m-%d-%H_%M_%S 86400 -300" combined env=!spam
Alias /awstatclasses "/usr/local/www/awstats/classes/"
Alias /awstatscss "/usr/local/www/awstats/css/"
Alias /awstatsicons "/usr/local/www/awstats/icons/"
ScriptAlias /awstats/ "/usr/local/www/awstats/cgi-bin/"
<Directory "/usr/local/www/awstats/">
Options None
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
/usr/local/etc/nginx/nginx.conf is a sparse, C-style hierarchical configuration file. It’s laid out basically like this:
general nginx settings: pid file, user, etc.
http {
various web-server-wide settings; log formats, include files, etc.
server {
virtual server 1 config here
}
server {
virtual server 2 config here
}
}
The first thing I need to change is the nginx error log. I rotate my web logs daily, and retain them indefinitely, in a file named by date. In Apache, I achieve this with rotatelogs(8), a program shipped with Apache. nginx doesn’t have this functionality; I must rotate my logs with an external script.
In the http section of the configuration file, I tell nginx where to put the main server logs.
http {
...
error_log /var/log/nginx/nginx-error.log;
access_log /var/log/nginx/nginx-access.log;
Define a virtual server and include the log statements:
http {
...
server {
server_name blackhelicopters.org www.blackhelicopters.org;
access_log /var/log/bh/bh-access.log;
error_log /var/log/bh/bh-error.log;
root /var/www/bh/;
}
}
That brings up the basic site and its logs. I don’t need to worry about the referral spam log, as I cannot separate it out. nginx doesn’t need ServerAlias entries; just list multiple server names.
To test the basic site, make an /etc/hosts entry on your desktop pointing the site to the new IP address, like so:
139.171.202.40 www.blackhelicopters.org
You desktop Web browser should use /etc/hosts over the DNS entry for that host, letting you call up the test site in your Web browser. Verify the site comes up, and that nginx is actually serving your content. Verify that the site’s access log contains your hits.
To rotate these logs regularly, create a script /usr/local/scripts/nginx-logrotate.sh.
#!/bin/sh
DATE=`date +%Y%m%d`
#main server
mv /var/log/nginx/nginx-error.log /var/log/nginx/nginx-error_$DATE.log
mv /var/log/nginx/nginx-access.log /var/log/nginx/nginx-access_$DATE.log
#bh.org
mv /var/log/bh/bh-error.log /var/log/bh/bh-error_$DATE.log
mv /var/log/bh/bh-access.log /var/log/bh/bh-access_$DATE.log
killall -s USR1 nginx
Run at 11:59 each night via cron(8).
59 23 * * * /usr/local/scripts/nginx-logrotate.sh
This won’t behave exactly like Apache’s logrotate. The current log file won’t have the date in its name. There will probably be some traffic between 11:59 PM and the start of the new day at 12:00AM. But it’s close enough for my purposes.
I must add entries for every site whose logs I want to rotate.
Now there’s the aliases. I don’t have awstats running on this new machine yet, but I want the Web server set up to support these aliases for later. Besides, you probably have aliases of your own you’d like to put in place. Define an alias within nginx.conf like so:
location ^~/awstatsclasses {
alias /usr/local/www/awstats/classes/;
}
location ^~/awstatscss {
alias /usr/local/www/awstats/css/;
}
location ^~/awstatsicons {
alias /usr/local/www/awstats/icons/;
}
Finally, I need my home directory’s public_html available as http://www.blackhelicopters.org/~mwlucas/. This doesn’t update, but people link here. The following snippet uses nginx’s regex functionality to simulate Apache’s mod_userdir.
location ~ ^/~(.+?)(/.*)?$ {
alias /home/$1/public_html$2;
index index.html index.htm;
autoindex on;
}
For most sites, I would define a useful error page. The purpose of this site is to say “don’t look here any more, look at the new Web site,” so pointing 404s to the index page is reasonable. Defining an error page like so:
error_page 404 /index.html;
The configuration for this entire site accumulates to:
server {
server_name blackhelicopters.org www.blackhelicopters.org;
access_log /var/log/bh/bh-access.log;
error_log /var/log/bh/bh-error.log;
root /var/www/bh/;
error_page 404 /index.html;
location ^~/awstatsclasses {
alias /usr/local/www/awstats/classes/;
}
location ^~/awstatscss {
alias /usr/local/www/awstats/css/;
}
location ^~/awstatsicons {
alias /usr/local/www/awstats/icons/;
}
location ~ ^/~(.+?)(/.*)?$ {
alias /home/$1/public_html$2;
index index.html index.htm;
autoindex on;
}
}
While I’m happy with nginx performance so far, I’m only running a couple of static sites on it. The real test will start once I use dynamic content.