Nginx with PHP as FastCGI on Gentoo Linux

Nginx (pronounced “Engine X”) is a high performance web server (and proxy server, but we will be using it as a web server here). In small VPS environments where memory is precious, Apache is at best overkill and uses memory that could be spent elsewhere (like MySQL query caching), at worst a terrible bottleneck that will consistently bring down your site under very moderate loads. If you are willing to live without .htaccess files and Apache-style mod_rewrite rules, Nginx is a great replacement that will lower memory usage and increase performance.

This is a draft. Comments, suggestions, corrections, improvements are very much welcome!

Lighttpd (pronounced “Lighty”) is another strong contender as a replacement web server for Apache, but reports (like this and this) of persistent memory leaks in Lighttpd led me to choose Nginx. Google for articles about Nginx yourself, or read some of these:

These instructions have been tested on a 256MB VPS slice at Slicehost. YMMV.

How do we make PHP work with Nginx? Nginx supports the FastCGI protocol, and can communicate with PHP running as a FastCGI process. When a user visits a PHP page, Nginx passes the request to PHP for processing, then passes the results from PHP back to the user. Unfortunately, Nginx does not have built-in support for starting PHP as a FastCGI daemon. Therefore, we will “borrow” Lighttpd’s handy spawn-fcgi application to help us make PHP run as a FastCGI daemon.

  1. Set the package-specific USE flags for a minimal installation.
    Open /etc/portage/package.use
    My flags are as follows:

    dev-db/mysql            -*
    dev-lang/php            -* cgi curl discard-path force-cgi-redirect threads bzip2 crypt ctype ftp gd hash mysql mysqli pcre posix session simplexml spl tokenizer xml xmlreader xmlwriter zip-external zlib
    net-misc/openssh        -* chroot hpn pam
    www-servers/lighttpd    -* fastcgi minimal
    www-servers/nginx       -* fastcgi pcre zlib
  2. Compile PHP, Lighttpd and Nginx. Note that Lighttpd will only take up a very small amount of disk space and consume no other system resources since we will not execute it. Go grab a drink.
    emerge -va php lighttpd nginx
  3. First, we must use spawn-fcgi to start PHP as a FastCGI daemon. The simplest way is to use Gentoo’s init script for spawn-fcgi.
    /etc/init.d/spawn-fcgi start
    rc-config add spawn-fcgi

    However, Gentoo’s spawn-fcgi init script has some limitations and funky behavior. It only allows you to run one single FastCGI application (i.e. you cannot run PHP and Rails with it). Also, php-cgi launched by spawn-fcgi seems to have the incorrect PID and username. Therefore, I decided to use an improved init script written by James Le Cuirot that is not included in Portage.

    James’s script (I named it spawn-fcgi-new) allows multiple FastCGI applications by using one symlink and one config file per application. Each of the symlinks, like fcgi.php or fcgi.rails, points to spawn-fcgi-new. Each of the symlinks have corresponding config files in /etc/conf.d, like fcgi.php or fcgi.rails. To launch an FastCGI daemon, one calls the appropriate symlink, like fcgi.php to start php-cgi. spawn-fcgi-new is never executed directly.

    Vote for this bug to encourage Gentoo devs to consider James’s script.

    Create /etc/init.d/spawn-fcgi-new with the following contents: (source)

    #!/sbin/runscript
    # Copyright 1999-2006 Gentoo Foundation
    # Distributed under the terms of the GNU General Public License v2
    # $Header: $
     
    FILENAME=$(basename "$1")
    PROGNAME=${FILENAME/fcgi./}
    SPAWNFCGI=/usr/bin/spawn-fcgi
    PIDPATH=/var/run/spawn-fcgi
    PIDFILE=${PIDPATH}/${PROGNAME}
     
    depend() {
    	need net
    }
     
    start() {
    	if [[ "${FILENAME}" == "spawn-fcgi" ]]; then
    		eerror "You are not supposed to run this script directly. Create a symlink"
    		eerror "for the FastCGI application you want to run as well as a copy of the"
    		eerror "configuration file and modify it appropriately like so..."
    		eerror
    		eerror "  ln -s spawn-fcgi /etc/init.d/fcgi.trac"
    		eerror "  cp /etc/conf.d/spawn-fcgi /etc/conf.d/fcgi.trac"
    		eerror "  $(basename "${EDITOR}") /etc/conf.d/fcgi.trac"
    		eerror
    		eerror "The new name must start with \"fcgi.\" or it won't work."
    		return 1
    	fi
     
    	local X E PHP_FCGI_CHILDREN_OPTION USER_OPTION GROUP_OPTION SOCKET_OPTION PORT_OPTION RETVAL
     
    	if [[ -z "${FCGI_WEB_SERVER_ADDRS}" ]]; then
    		FCGI_WEB_SERVER_ADDRS=127.0.0.1
    	fi
     
    	if [[ -n "${PHP_FCGI_CHILDREN}" ]]; then
    		PHP_FCGI_CHILDREN_OPTION="-C ${PHP_FCGI_CHILDREN}"
    	fi
     
    	if [[ -z "${FCGI_CHILDREN}" ]]; then
    		FCGI_CHILDREN=1
    	fi
     
    	if [[ -n "${FCGI_USER}" ]] && [[ "${FCGI_USER}" != "root" ]]; then
    		USER_OPTION="-u ${FCGI_USER}"
    	fi
     
    	if [[ -n "${FCGI_GROUP}" ]] && [[ "${FCGI_GROUP}" != "root" ]]; then
    		GROUP_OPTION="-g ${FCGI_GROUP}"
    	fi
     
    	ALLOWED_ENV="$ALLOWED_ENV USER GROUPS PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS RAILS_ENV TRAC_ENV_PARENT_DIR TRAC_ENV"
    	unset E
     
    	for i in ${ALLOWED_ENV}; do
    		[[ -n "${!i}" ]] && E="${E} ${i}=${!i}"
    	done
     
    	# Store all spawn-fcgi PIDs in the same place.
    	install -d "${PIDPATH}" -m 0700 -o root
     
    	ebegin "Starting FastCGI application ${PROGNAME}"
    	for X in `seq 1 ${FCGI_CHILDREN}`; do
    		[[ -n "${FCGI_SOCKET}" ]] && SOCKET_OPTION="-s ${FCGI_SOCKET}-${X}"
    		[[ -n "${FCGI_PORT}" ]] && PORT_OPTION="-p $((${FCGI_PORT} + ${X} - 1))"
     
    		env - ${E} ${SPAWNFCGI} ${SOCKET_OPTION} ${PORT_OPTION} -f ${FCGI_PROGRAM} -P ${PIDFILE}-${X}.pid ${USER_OPTION} ${GROUP_OPTION} ${PHP_FCGI_CHILDREN_OPTION} &> /dev/null
    		RETVAL=$?
     
    		# Stop on error. Dont want to spawn a mess!
    		[[ "${RETVAL}" != "0" ]] && break
    	done
    	eend ${RETVAL}
    }
     
    stop() {
    	local X RETVAL
     
    	ebegin "Stopping FastCGI application ${PROGNAME}"
    	kill `for X in ${PIDFILE}-[0-9]*.pid; do cat $X; echo -n ' '; done` &> /dev/null
    	RETVAL=$?
    	eend ${RETVAL}
     
            rm -f ${PIDFILE}-[0-9]*.pid
            return $RETVAL
    }
  4. Make spawn-fcgi-new executable
    chmod +x /etc/init.d/spawn-fcgi-new
  5. Create a symlink for launching php-cgi, called fcgi.php
    ln -s /etc/init.d/spawn-fcgi-new /etc/init.d/fcgi.php

    Now /etc/init.d/fcgi.php points to /etc/init.d/spawn-fcgi-new.

  6. Create a corresponding config file for fcgi.php
    Create /etc/conf.d/fcgi.php with the following contents: (source)

    # Copyright 1999-2006 Gentoo Foundation
    # Distributed under the terms of the GNU General Public License v2
    # $Header: $
     
    # DO NOT MODIFY THIS FILE DIRECTLY! CREATE A COPY AND MODIFY THAT INSTEAD!
     
    # One of the following options must be enabled. The filename specified by 
    # FCGI_SOCKET will be suffixed with a number for each child process, for
    # example, fcgi.socket-1. The port specified by FCGI_PORT is the port used
    # by the first child process. If this is set to 1234 then subsequent child
    # processes will use 1235, 1236, etc.
    #
    #FCGI_SOCKET=/path/to/fcgi.socket
    FCGI_PORT=1026
     
    # When using FCGI_PORT, connections will only be accepted from the
    # following addresses. The default is 127.0.0.1.
    #
    FCGI_WEB_SERVER_ADDRS=127.0.0.1
     
    # The path to your FastCGI application. These sometimes carry the .fcgi
    # extension but not always. For PHP, you should usually point this to
    # /usr/bin/php-cgi.
    #
    FCGI_PROGRAM=/usr/bin/php-cgi
     
    # The number of child processes to spawn. The default is 1. For PHP
    # applications, set this to 1 and use PHP_FCGI_CHILDREN instead.
    #
    FCGI_CHILDREN=1
     
    # The user and group to run your application as. If you do not specify
    # these, the application will be run as root:root.
    #
    FCGI_USER=nginx
    FCGI_GROUP=nginx
     
    # If your application requires additional environment variables, you need
    # to allow them here. Only the variables specified here, plus a few others
    # mentioned in the init script are passed to the application.
    #
    ALLOWED_ENV="PATH USER"
     
    # PHP ONLY :: These two options are specific to PHP. The first is
    # the number of child processes to spawn. The second is the number
    # of requests to be served by a single PHP processes before it is
    # restarted.
    #
    PHP_FCGI_CHILDREN=5
    PHP_FCGI_MAX_REQUESTS=500
  7. Start PHP as a FastCGI process and make it always start on bootup.
    /etc/init.d/fcgi.php start
    rc-config add fcgi.php
  8. Now that the php-cgi daemon is all set, we must configure Nginx to pass PHP requests onto php-cgi.
    Edit /etc/nginx/nginx.conf
    Insert the following in the “server { }” section, somewhere after the opening brace and before the closing brace

                    location ~ .*.php$ {
                            include /etc/nginx/fastcgi_params;
                            fastcgi_pass    127.0.0.1:1026;
                            fastcgi_index   index.php;
                            fastcgi_param   SCRIPT_FILENAME  /var/www/localhost/htdocs$fastcgi_script_name;
                    }

    /var/www/localhost/htdocs/” should be replaced with the path to your web root.
    Make any other configuration changes that are necessary (will probably be covered in another post), like

                    listen          YOUR.IP.ADDRESS.HERE;
  9. Start Nginx and make it always start on bootup.
    /etc/init.d/nginx start
    rc-config add nginx

Viola, you should now have a web server with PHP support. Try creating a dummy PHP file to make sure all is well.

< ?php phpinfo() ?>

Credit:

To-Do:
Replace spawn-fcgi with a generic script or a Gentoo-endorsed solution. Possible leads that I haven’t been able to figure out:

If you liked this post, please subscribe to my feed. Thanks for visiting!

Related posts

9 Responses to “Nginx with PHP as FastCGI on Gentoo Linux”


  1. 1 zk

    Thanks for the tip on the new spawn-fcgi script. I’ve actually been using a script I wrote for myself, but the script referenced has some obvious advantages, :P

    You should definitely tack on support for some sort of php caching…I like eaccelerator, myself.

    just run: ‘HTTPD_USER=”nginx” HTTPD_GROUP=”nginx” emerge dev-php5/eaccelerator’

    Respawn your threads, check phpinfo(), and you should be good to go.

  2. 2 Tummblr

    Hi zk,

    Thanks for your comment. I actually use XCache myself. I documented the (simple) setup steps here:
    http://www.tummblr.com/linux/speed-up-php-with-xcache-on-gentoo-linux/

  3. 3 p3rseus

    hi,

    doesn’t work for me. /etc/init.d/fcgi.php start fail. i don’t find any log related.

    i have php 5.2.5 installed and everything seems to be at the right place

    any idea?

  4. 4 Rick

    Same here… I *think* this is the error:

    trap ‘eerror “ERROR: ${SVCNAME} caught an interrupt”; exit 1′ INT QUIT TSTP

  5. 5 Rick

    I spoke too soon. I didn’t install lighttpd :/

  6. 6 Tummblr

    @Rick: Glad it worked out for you. I neglected to install Lighttpd myself once while following my own instructions. :/

    @p3rseus: Thanks for your comment. Unfortunately, I can’t really help without more information. :( Good luck!

  7. 7 movielady

    Question: since you’re using the spawn-fcgi-new script, why does one need to install lighttpd at all? I thought the only reason for installing it was to grab the spawn-fcgi script, and since James’ script works so much better…

    Thank you very much for the tutorials, btw. :)

  8. 8 mike503

    spawn-fcgi sucks. It doesn’t even spawn properly. Unless recently fixed. When your engines hit PHP_FCGI_MAX_REQUESTS, it should kill off the child process and restart it.

    What I noticed was spawn-fcgi wouldn’t. What I failed to figure out is why spawn-fcgi is needed at all?

    Use upstart or daemontools or even init, and launch your engines like so:

    Assuming PHP_FCGI_MAX_REQUESTS and PHP_FCGI_CHILDREN are set…

    /usr/bin/sudo -u {user} /usr/local/bin/php-cgi -b {address/port}

    PHP’s built-in support properly spawns them and recycles them as desired.

    I had ideas for a FastCGI process manager, and it looks like that idea is not needed anymore, since someone has created php-fpm. A patch that fixes some annoyances in PHP’s built-in FastCGI SAPI and adds support to spawn engines as different user/group and [soon] Apache-style adaptive process spawning.

    It’s almost 100% perfect now, I’m excited for Andrei to complete it… use Google to translate it from Russian :)

    http://php-fpm.anight.org/

    I’ve converted all my business and corporate stuff to use PHP-FPM now. Soon I will follow with nginx… Lighttpd had a few bugs (one very obvious one) that -just- got fixed. Jan’s a crazy guy but it does seem like they have been working out new stuff and not keeping up on the old as much.

    Neither server does .htaccess though, but nginx at least has easier Apache style -f/-d rewrite stuff and supports SSI and PHP in the same file (a client’s website… don’t ask) - something that I could not get Lighty to accept (although it shouldn’t need to, this website is an exception)

  1. 1 spiceee.com » pensaletes

Leave a Reply