It’s been a while since I posted about my current webserver setup, and a few things have changed around here! I like to stay current, and the latest version of PHP has changed the way I build from source. I’ve also changed the way I install stuff, this way it’s much easier to update things like PHP which tend to litter files all over the place. Anyway, you came here for the goods, so here we go:
Step 1: Environment and Pre-Requisites
This section probably looks familiar. The only difference now is that I’m running Ubuntu 10.04 LTS on my main server now. The packages are pretty much the same. First we make sure the universe repository is enabled by editing /etc/apt/sources.list and removing the hash marks in front of universe lines:
deb http://us.archive.ubuntu.com/ubuntu/ lucid universe multiverse deb-src http://us.archive.ubuntu.com/ubuntu/ lucid universe multiverse deb http://us.archive.ubuntu.com/ubuntu/ lucid-updates universe restricted main multiverse deb-src http://us.archive.ubuntu.com/ubuntu/ lucid-updates universe multiverse deb http://security.ubuntu.com/ubuntu lucid-security universe multiverse deb-src http://security.ubuntu.com/ubuntu lucid-security universe multiverse
Next we need to snag a lot of packages that are requried to build the various packages. Run the following commands to get what we need:
sudo apt-get update sudo apt-get install build-essential libxml2-dev libssl-dev libbz2-dev curl libcurl4-openssl-dev libpng12-dev libmcrypt-dev mysql-server libmysqlclient-dev libxslt1-dev autoconf2.13 libltdl-dev libreadline5-dev libsqlite3-ruby postgresql-server-dev-8.4 libpcre3-dev
Finally, libevent still hasn’t been updated to a suitable version for PHP-FPM, so we need to install that manually:
wget http://monkey.org/~provos/libevent-1.4.14b-stable.tar.gz cd libevent-1.4.14b-stable ./configure make && sudo checkinstall
You’ll notice above that I use the "checkinstall" command to install this package. The main reason I use this is that it makes upgrading a lot easier in the future. It’s especially nice when you want to update something like PHP. This handy command will generate a nice *.deb file for you, and then you can remove the package using “dpkg –r packagename” when you’re ready to upgrade down the road. Neat, huh? Note: if you get an error during building the package with checkinstall, it’s probably because you need to change the version of your package to say “1.4.14b-stable” instead of just “stable.” For reference, here are the checkinstall values I use:
This package will be built according to these values: 0 - Maintainer: [ Scott Anderson <scott@geeksharp.com> ] 1 - Summary: [ My custom version of libevent-1.4.14b-stable ] 2 - Name: [ libevent ] 3 - Version: [ 1.4.14b-stable ] 4 - Release: [ 1 ] 5 - License: [ GPL ] 6 - Group: [ checkinstall ] 7 - Architecture: [ i386 ] 8 - Source location: [ libevent-1.4.14b-stable ] 9 - Alternate source location: [ ] 10 - Requires: [ ] 11 - Provides: [ libevent ]
Step 2: Compiling PHP From Source with the Suhosin Patch
Since the FPM module is now included in PHP as of version 5.3.3, we don’t have to download it separately. All we need to snag is the latest Suhosin patch and apply to to the 5.3.3 source.
wget http://us2.php.net/get/php-5.3.3.tar.bz2/from/this/mirror bzip2 -dc php-5.3.3.tar.bz2 | tar xf - wget http://download.suhosin.org/suhosin-patch-5.3.3-0.9.10.patch.gz gunzip suhosin-patch-5.3.3-0.9.10.patch.gz cd php-5.3.3 patch -p 1 -i ../suhosin-patch-5.3.3-0.9.10.patch
Now all we have to do is configure and build PHP itself. Thankfully this process is a lot simpler now that PHP-FPM has been integrated into the core PHP source.
./configure --enable-bcmath --with-bz2 --enable-calendar --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --with-curl --enable-dba --enable-exif --enable-ftp --with-gd --with-gettext --enable-mbstring --with-mysql --with-mysqli --with-pdo-mysql --with-openssl --with-pcre-regex --enable-shmop --enable-soap --enable-sockets --enable-sysvmsg --enable-wddx --enable-zip --with-zlib --enable-sysvsem --enable-sysvshm --with-mcrypt --enable-pcntl --enable-mbregex --with-mhash --with-xsl make && sudo checkinstall
Now we need to copy the init.d script and the new php-fpm.conf into place. Even if you already had PHP-FPM installed, these steps are essential because the configuration format has changed, and we need to modify a couple of settings. You can move the new files into place like so:
sudo cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm sudo chmod +x /etc/init.d/php-fpm sudo cp sapi/fpm/php-fpm.conf /usr/local/etc/php-fpm.conf sudo cp php.ini-production /usr/local/lib/php.ini
There are a few things you’ll need to change in the config file before we can actually start PHP. Make sure you open /usr/local/etc/php-fpm.conf in your favorite text editor and change the following values. Please note that the “pm” settings are what I use on my VPS, but you might want to adjust them to better suit your environment.
pid = /usr/local/var/run/php-fpm.pid pm.max_children = 10 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 5
Now you can stop and start PHP-FPM like so:
sudo /etc/init.d/php-fpm restart
Step 3: Ruby Enterprise Edition, Nginx, and Phusion Passenger
By far the best way to run a Rails site in production is by using Phusion Passenger and Ruby Enterprise Edition. The performance benefits are huge, and this setup is very friendly to limited resource environments (like VPS’s). During the installation of Ruby Enterprise Edition, we will have the opportunity to customize our Nginx install, so we need to snag its source too. Here’s how:
wget http://nginx.org/download/nginx-0.7.67.tar.gz wget http://rubyforge.org/frs/download.php/71096/ruby-enterprise-1.8.7-2010.02.tar.gz tar -zxf nginx-0.7.67.tar.gz tar -zxf ruby-enterprise-1.8.7-2010.02.tar.gz ./ruby-enterprise-1.8.7-2010.02/installer
This install process is pretty seamless. Once you’re done, you need to run one more command to enable Phusion Passenger support in Nginx:
sudo /opt/ruby-enterprise-1.8.7-2010.02/bin/passenger-install-nginx-module
This install script will ask you a series of questions. I prefer to customize my Nginx install, so here are my answers. Obviously, you’ll need to adjust any paths to places appropriate for your system and where you extracted the nginx source tarball.
Enter your choice (1 or 2) or press Ctrl-C to abort: 2 Please specify the directory: <enter your directory, in my case it was /home/scott/nginx-0.7.67> Please specify a prefix directory [/opt/nginx]: /usr/local/nginx Extra arguments to pass to configure script: --sbin-path=/usr/local/sbin --with-http_ssl_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --with-http_stub_status_module --with-http_gzip_static_module
Step 4: Nginx Configuration
We took care of PHP above, so all we need to do now is configure Nginx. My default configuration file is below. You can tweak it however you like, but these settings work really well in my environment. If you used the same options as I did above, you can find this file at /usr/local/nginx/conf/nginx.conf:
user www-data;
worker_processes 4;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 10 10;
passenger_root /opt/ruby-enterprise-1.8.7-2010.02/lib/ruby/gems/1.8/gems/passenger-2.2.15;
passenger_ruby /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby;
passenger_pool_idle_time 1000;
gzip on;
gzip_comp_level 1;
gzip_proxied any;
gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx_access.log main;
error_log /var/log/nginx_error.log crit;
include /usr/local/nginx/sites-enabled/*;
}
Now we need to set some default fastcgi options in /usr/local/nginx/conf/fastgci_params. I just appended these to the end of the file.
fastcgi_connect_timeout 60; fastcgi_send_timeout 180; fastcgi_read_timeout 180; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_intercept_errors on;
Next, I like to create a "/usr/local/nginx/sites-enabled" directory that houses each website’s configuration details. First I’ll show the configuration I used to handle URL rewrites for WordPress (this blog). I called the file /usr/local/nginx/sites-enabled/geeksharp.com.
server {
listen 80;
server_name *.geeksharp.com *.geeksharp.info *.geeksharp.org geeksharp.info geeksharp.org;
rewrite ^(.*) http://geeksharp.com$1 permanent;
}
server {
listen 80;
server_name geeksharp.com;
location / {
root /var/www; # absolute path to your WordPress installation
index index.php index.html index.htm;
# this serves static files that exist without running other rewrite tests
if (-f $request_filename) {
expires 30d;
break;
}
# this sends all non-existing file or directory requests to index.php
if (!-e $request_filename) {
rewrite ^(.+)$ /index.php?q=$1 last;
}
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www$fastcgi_script_name;
include fastcgi_params;
}
}
Here’s how I’d set up a rails site. In this example, the file is called /usr/local/nginx/sites-enabled/geeksharp.net.
server {
listen 80;
server_name *.geeksharp.net;
rewrite ^(.*) http://geeksharp.net$1 permanent;
}
server {
listen 80;
server_name geeksharp.net;
root /home/scott/geeksharp.net/public;
passenger_enabled on;
access_log /home/scott/geeksharp.net-access.log;
error_log /home/scott/geeksharp.net-error.log;
}
Step 5: Startup Scripts
We already copied the PHP startup script into place earlier, however we need to create one for Nginx, too. Place this file at /etc/init.d/nginx and use the following contents:
#! /bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the nginx web server
# Description: starts nginx using start-stop-daemon
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/sbin/nginx
NAME=nginx
DESC=nginx
test -x $DAEMON || exit 0
# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
. /etc/default/nginx
fi
set -e
case "$1" in
start)
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
--exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
--exec $DAEMON
echo "$NAME."
;;
restart|force-reload)
echo -n "Restarting $DESC: "
start-stop-daemon --stop --quiet --pidfile \
/usr/local/nginx/logs/$NAME.pid --exec $DAEMON
sleep 1
start-stop-daemon --start --quiet --pidfile \
/usr/local/nginx/logs/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
reload)
echo -n "Reloading $DESC configuration: "
start-stop-daemon --stop --signal HUP --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
--exec $DAEMON
echo "$NAME."
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0
Finally, make sure you update your rc.d
sudo update-rc.d nginx defaults sudo update-rc.d php-fpm defaults
Please note that if for some reason nginx won’t start, chances are you have a config issue. When you issue a restart command, the config errors usually will not be displayed. The best way to track them down is to is to simply issue a stop command followed by a start command, and your errors will be displayed. If you ever need to restart your services manually, you can use these commands (I find that restart can be buggy, which is why I explicity stop and start each one):
sudo /etc/init.d/php-fpm stop && sudo /etc/init.d/php-fpm start sudo /etc/init.d/nginx stop && sudo /etc/init.d/nginx start
So that’s it! I know this is an epic post, but I hope it helps someone. Leave comments if you have specific questions or if I left out something particularly useful. Again, I understand that there’s probably a better way to do something, so feel free to share your thoughts! As a final thought, I would like to thank Joshua Dorkin for providing a great article on which I based a lot of this material!
Tags: Ruby, Ruby on Rails
Hi! My name is Scott. Welcome to my place on the web. I currently work in St. Louis, Missouri as a Java EE developer. I also do a lot of work with ASP.NET and Node.js in my spare time.