Nginx, WordPress & fun

Well, after some months (ok, years) of seeing other people blogs, I’ve decided to open my own weblog. As wannabe geek, the most interesting part of the idea isn’t about writing posts but in installing and managing the software. So I’ve searched for a suitable application and, while I was at it, even the right web server, just to add some fun. I’ve always used apache and I like it, but why don’t try to change? So I’ve given a try to Nginx, web server similar to lighttpd wrote by a Russian guy (and with a lot of documentation in russian, too). And it worked like a charm.

As blog application, I’ve installed WordPress: the Gentoo people reports some security issues with this software, but it’s nice and with a huge user base, so I decided to unmask it and use it anyway. I don’t think I’ll be killed by some security issues anyway, at least for my blog :)

Since I’ve had to get rid of some installation issues and quirks, I’m writing the steps I followed as a reminder, just in case; maybe it can even be helpful for someone doing the same installation.

And yes, I hope to collect suggestions on how to do better what I’ve done so far. Basically, I don’t know If I made incredible mistakes, I only know that it seems to work, given the fact that you are reading this page :)

So be careful: I’m not a programmer and I’ve tweaked this setup just for fun; it’s not guaranteed to work and it may contain any kind of errors and horrors. If you follow my indications please let me know if it works and correct me where I’m wrong :)

Let’s start:

First: Nginx. I use Gentoo distro that has nginx packaged, so an

emerge nginx

it’s enough. Please note that my installation is a “~ “, that in Gentoo slang means “experimental”. As I’m writing, with “~” you will get nginx 0.6.X, without “~” nginx 0.5.X. I like to be on the edge, so I’m fine with 0.6 version that is the “devel” branch right now. As usual, YMMV.

Now you have to configure it; you can find how to do it on standard documentation, so nothing difficult here. You should be able to have Nginx serving static pages in a very short time.

The WordPress installation is more or less at the same level of difficulty, you only have to unmask WordPress package. Due to security concerns of gentoo developers, emerge will refuse to install it. (Please note: Gentoo guys are good at security and I advise you to follow their directions; said in another way, if you unmask some packages or simply use ~ versions, you’re on your own. Don’t complain with me if your machine get damaged, your installation visited and used as playfield by some cracker and your coffee begins to smell horribly. I warned you).

so:

place in /etc/portage/package.unmask a line similar to this:

www-apps/wordpress

or if you want to stick with a specific version,

=www-apps/wordpress-2.2.2

and then

emerge wordpress

Now starts the fun part. To complete your wordpress post-installation procedure, you need to access the admin web page, but nginx cannot yet allow you to do so.

Nginx doesn’t know how to run php scripts, so you have to use a fast-cgi backend approach. Basically this means that you have to run a process (one or more, according to the number of hits you’re expecting for your site) that can handle php requests. Nginx will pass client requests to this process and pass back to the browser the result. So you need to fire up fastcgi php process before runnung nginx. This can be done with spawn-fcgi, an utility that comes with lighttpd package. So, as you imagine, another emerge is coming:

emerge lighttpd

You will be using only a small utility provided by lighttp guys, so don’t bother to configure lighttpd. It would be really great if some gentoo packager decides to make a package just for spawn-fcgi alone…hint, hint :)

Now you should have all the pieces needed to start your brand new wordpress installation, let’s tie all together.

Nginx configuration:

follow the documentation! :) For the lazy, there is a snippet from my nginx conf file:

        server {

               ...usual stuff...

               include /etc/nginx/enable_wordpress;

                # .php sent to php

                location ~ .*.php$ {

                        include /etc/nginx/fastcgi_params;

                        fastcgi_pass  127.0.0.1:1026;

                        fastcgi_index index.php;

                }

}

/etc/nginx/fastcgi_params:

fastcgi_param  QUERY_STRING       $query_string;

fastcgi_param  REQUEST_METHOD     $request_method;

fastcgi_param  CONTENT_TYPE       $content_type;

fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;

fastcgi_param  SCRIPT_FILENAME    $fastcgi_script_name;

fastcgi_param  REQUEST_URI        $request_uri;

fastcgi_param  DOCUMENT_URI       $document_uri;

fastcgi_param  DOCUMENT_ROOT      $document_root;

fastcgi_param  SERVER_PROTOCOL    $server_protocol;fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;

fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;

fastcgi_param  REMOTE_PORT        $remote_port;

fastcgi_param  SERVER_ADDR        $server_addr;

fastcgi_param  SERVER_PORT        $server_port;

fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect

fastcgi_param  REDIRECT_STATUS    200;

I’ve found those parameters in several examples on the web and tweaked it a bit. I can’t say if they are all needed or not, but it works for me.

/etc/nginx/enable_wordpress

# wordpress pretty urls

# http://drupal.org/node/110224

 location / {

 	index index.php;

 	if (-f $request_filename) {

 		break;

 	}

 if (-d $request_filename) {

 	break;

 }

 rewrite ^(.+)$ /index.php?q=$1 last;

}

The last file is needed if you don’t use the default permalink structure for wordpress. If you select an option other than “Default” in Option->Permalinks configuration page, WordPress will kindly provide you rewrite rules for apache, but you are using nginx. You have to translate the rules suggested bu WP in something useful for your webserver. The one reported here works with “Custom Structure” set to “/%postname%/. See the link at top of the snippet for more details.

At this point nginx should be fine. Now you have to set up the “backend” part, the fcgi process. Here you have to use spawn-fcgi that comes from lighttpd. You will find a suitable init script: /etc/init.d/spawn-fcgi; anyway I don’t like this script provided by gentoo package (for the reasons described here) so I grabbed the init script provided in this bug report and tweaked it a bit, as follows:

#!/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-fcgi2" ]]; 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-fcgi2 /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 "${USERID}" ]] && [[ "${USERID}" != "root" ]]; then

 	USER_OPTION="-u ${USERID}"

 fi

if [[ -n "${GROUPID}" ]] && [[ "${GROUPID}" != "root" ]]; then

 	GROUP_OPTION="-g ${GROUPID}"

 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_SOCKET}" ]] && SOCKET_PATH="${FCGI_SOCKET}-${X}"

 [[ -n "${SOCKET_PATH}" ]] && E="${E} SOCKET_PATH=${SOCKET_PATH}"

 [[ -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. Do not 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

}

The corresponding configuration file is:
/etc/conf.d/fcgi.php

# Copyright 1999-2004 Gentoo Foundation

# Distributed under the terms of the GNU General Public License v2

# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/spawn-fcgi.confd,v 1.1 2005/02/14 11:39:01 ka0ttic Exp $# Configuration file for the FCGI-Part of /etc/init.d/lighttpd

## Set this to "yes" to enable SPAWNFCGI

ENABLE_SPAWNFCGI="yes"

## ABSOLUTE path to the spawn-fcgi binary

SPAWNFCGI="/usr/bin/spawn-fcgi"

## ABSOLUTE path to the PHP binary

FCGI_PROGRAM="/usr/bin/php-cgi"

## bind to tcp-port on localhost

FCGI_PORT="1026"

## number of PHP childs to spawn

PHP_FCGI_CHILDREN=5

## number of request server by a single php-process until is will be restarted

PHP_FCGI_MAX_REQUESTS=1000

## IP adresses where PHP should access server connections from

FCGI_WEB_SERVER_ADDRS="127.0.0.1"

# allowed environment variables sperated by spaces

ALLOWED_ENV="PATH USER"

# do NOT change line below

ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS"

## if this script is run as root switch to the following user

USERID=apache

GROUPID=apache

please note that this script use an approach like “net” startup script, where you don’t call it directly but you use a symlink with the proper name. I’ve renamed the original init script (spawn-fcgi) as spawn-fcgi2 to avoid package clashes.

In more general terms, the symlink structure is:

fcgi.myapp -> spawn.fcgi2 in /etc/init.d

and the corresponding conf file becomes:

fcgi.myapp in /etc/conf.d

so just put a symlink like

fcgi.php->spawn-fcgi2 in /etc/init.d/

and you should be set to run your fcgi application.
(NOTE: don’t forget to enable cgi and force-cgi-redirect in your use flags for your php package)

Now start the fcgi app, nginx, point your browser to wordpress setup url and tell me what happened :)

…the end?

No, at least for me, because I discovered that one perl cgi application on my web server was no longer working. Easy enough to explain, given that nginx can’t handle cgi and so far I’ve configured only php scripts.

Why not use the same approach for perl? I’ve googled a bit and found a nice script on nginx wiki, and it worked. Basically, you have to fire up perl cgi process in the same way you did for php and pass to it all .pl cgi requests. The problem is that this script can handle only one request: if you get more than one request the second to come has to wait the first to be served.

For the traffic that hits my site (say one visitor per day) this is not a big issue :). But of course is not a good setup, so I’ve tried to improve it.

I’ve tweaked a bit this startup script, making it able to be launched more times by the same spawn-fcgi utility used for php.

This is the modified script:

#!/usr/bin/perluse FCGI;

use Socket;

#this keeps the program alive or something after exec'ing perl scripts

END() { } BEGIN() { }

*CORE::GLOBAL::exit = sub { die "fakeexitnrc=".shift()."n"; }; eval q{exit}; if ($@) { exit unless $@ =~ /^fakeexit/; } ;

&main;

sub main {

        $socket = FCGI::OpenSocket( $ENV{"SOCKET_PATH"}, 10 ); #use UNIX sockets - user running this script must have w access to the 'nginx' folder!!

        $request = FCGI::Request( *STDIN, *STDOUT, *STDERR, %req_params, $socket );

        if ($request) { request_loop()};

                FCGI::CloseSocket( $socket );

}

sub request_loop {

        while( $request->Accept() >= 0 ) {

                $stdin_passthrough ='';

                $req_len = 0 + $req_params{'CONTENT_LENGTH'};

                if (($req_params{'REQUEST_METHOD'} eq 'POST') && ($req_len != 0) ){

                        while ($req_len) {

                                $stdin_passthrough .= getc(STDIN);

                                $req_len--;

                        }

                } else {

                        $stdin_passthrough = $req_params{QUERY_STRING};

                }

        #running the cgi app

                if ( (-x $req_params{SCRIPT_FILENAME}) &&  #can I execute this?

                (-s $req_params{SCRIPT_FILENAME}) &&  #Is this file empty?

                (-r $req_params{SCRIPT_FILENAME})     #can I read this file?

                ){

                        #http://perldoc.perl.org/perlipc.html#Safe-Pipe-Opens

#                       open $cgi_app, '-|', $req_params{SCRIPT_FILENAME}, $stdin_passthrough or print("Content-type: text/plainrnrn"); print "Rrror: CGI app returned no output - Executing $req_params{SCRIPT_FILENAME} failed !n";

                        open $cgi_app, '-|', $req_params{SCRIPT_FILENAME}, $stdin_passthrough;

                        if ($cgi_app) {print <$cgi_app>; close $cgi_app;}

                }

                else {

                        print("Content-type: text/plainrnrn");

                        print "Error: No such CGI app - $req_params{SCRIPT_FILENAME} may not exist or is not executable by this process.n";

                }

        }

}

Of course you have to add another symlink in init.d:

fcgi.perl->spawn-fcgi2 in /etc/init.d/

and another cfg file in conf.d:

# Copyright 1999-2004 Gentoo Foundation

# Distributed under the terms of the GNU General Public License v2

# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/spawn-fcgi.confd,v 1.1 2005/02/14 11:39:01 ka0ttic Exp $# Configuration file for the FCGI-Part of /etc/init.d/lighttpd

## Set this to "yes" to enable SPAWNFCGI

ENABLE_SPAWNFCGI="yes"

## ABSOLUTE path to the spawn-fcgi binary

SPAWNFCGI="/usr/bin/spawn-fcgi"

## ABSOLUTE path to the PHP binary

FCGI_PROGRAM="/usr/bin/perl-fcgi.pl"

## bind to tcp-port on localhost

FCGI_SOCKET="/var/run/nginx/fcgi"

## number of PERL childs to spawn

FCGI_CHILDREN=5

## number of request server by a single php-process until is will be restarted

PERL_FCGI_MAX_REQUESTS=1000

## IP adresses where PHP should access server connections from

FCGI_WEB_SERVER_ADDRS="127.0.0.1"

# allowed environment variables sperated by spaces

ALLOWED_ENV="PATH USER"

# do NOT change line below

ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS"

## if this script is run as root switch to the following user

USERID=apache

GROUPID=apache

The difference in this case is that nginx must contact the running cgi process using a unix socket, an not a tcp connection as for php. Nginx must be configured accordingly. Nothing difficult, but there is another issue. AFAIK, while php fcgi can be invoked from nginx using the same tcp connection for many clients, for unix sockets you must use different sockets for different incoming requests. Basically each perl process creates a socket and nginx must know where to find them, one by one. To handle this I’ve used the load balancing capability of nginx: I’ve configured a pool of unix sockets in nginx as a “cluster” of backends.
the relevant lines in ngix conf file are:

upstream  backend  {

    server   unix:/var/run/nginx/fcgi-1;

    server   unix:/var/run/nginx/fcgi-2;

    server   unix:/var/run/nginx/fcgi-3;

    server   unix:/var/run/nginx/fcgi-4;

    server   unix:/var/run/nginx/fcgi-5;

}

(of course place it outside server block!)
and, this time inside server block,

location ~ ^/cgi-bin/.*.cgi$ {

    fastcgi_pass  backend;

    fastcgi_index index.cgi;

     include /etc/nginx/fastcgi_params;

    fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;

}

Basically, with the fcgi.perl startup script we will start up as many perl processes as configured in conf file (in my case, 5); the script is smart enough to create 5 unix sockets, each one with a different filename (say: filename-1, filename-2 and so on).
nginx is then configured to contact those sockets and use it in a pool. It should be able to manage 5 request at the same time.

FInal words: I don’t know if this approach is the right one, but it works in my small (I’d say minimal) installation. If you have hints or corrections please let me know, even if you consider this setup completely brain damaged :) As said at the beginning, the fun part is not to use it, but to install it…

Have Fun.

Print This Post Print This Post

3 comments ↓

#1 Nginx with PHP as FastCGI on Gentoo Linux | Tummblr on 12.23.07 at 21:50

[...] Nginx, WordPress & fun [...]

#2 Joey K on 12.28.07 at 04:21

Heya - I’m the one who put up the cgi wrap script in its quoted form. I’ve just recently noticed that it can’t multitask and, lo, I find the first reporter here!

I’m trying to see if FCGI::ProcManager (http://search.cpan.org/~gbjk/FCGI-ProcManager-0.18/) will help it out, probably by internally forking itself - in effect doing what you have rigged spawn-fcgi to do for the wrapper script.

If I figure it out, I’ll update the script on the Nginx wiki.

#3 Aaron Schaefer on 02.27.08 at 19:08

I wrote a more generic (not Gentoo-specific) set of instructions on how to install WordPress with Nginx with an init script example for Arch Linux. In case anyone is interested: http://elasticdog.com/2008/02/howto-install-wordpress-on-nginx/

Leave a Comment

Powered by WP Hashcash