Update: if you want to use Homebrew instead of MacPorts you can get additional information on how to install PHP with Homebrew here Installing PHP 5.3 with Homebrew on 10.6 Snow Leopard, nginx is available in Homebrew in default distribution

Update: Here is the new tip about how to run nginx web server on the 80 port on MacOS X machine Running homebrewed Nginx with sudo on MacOS X

These are instructions for how to install nginx (pronounced as “Engine X”, a high performance open source web server) and PHP with FastCGI on a Mac for development purposes. I spent quite a bit of time figuring out how to do this on my own, so hopefully it will save someone else a little time. For the record, I was using Mac OS X 10.5 (Leopard) on a white Intel Core Duo MacBook at the time of this writing. There isn’t anything terribly nonstandard about my particular setup that is worth mentioning, other than a larger than standard hard drive and 3rd party RAM. Obviously, none of this will work on a PC running Windows or Linux, although there are a bunch of places you can go to find out how to install nginx on other platforms. This article, however, focuses only on the Mac platform.

Update: I am having a bear of a time with the launchd plist for starting up the PHP FastCGI daemon. I can’t find the reason why this plist, after loading successfully with the launchctl utility, doesn’t load at all upon the next startup. Meanwhile, plenty of other launchd plists have no problem loading on startup. Baffled for the moment, and hating plists right now. Why couldn’t Apple have just gone with more conventional init scripts for spawning daemon processes at startup?

Many thanks to Dan Benjamin and Robby Russell, whose articles on similar subjects have helped me numerous times in the past, and inspired me to attempt a walkthrough of my own.

Most importantly, I must stress that the contents of this article are provided as is, I do not claim to be an expert in any of the material I cover here, and I will be unable to provide technical support for these solutions. Feel free to leave a comment, of course, and I’ll do my best to correct any errata! I am not underwritten or funded by any professional organization, and everything I am writing is, to the best of my knowledge, as accurate as I can make it. This is the fruit of my own personal trial and error, and is also based on various sources I found on the Web. At the bottom, I’ve given attribution to these sources as I can recall.

Preamble

What you’re thinking is absolutely true. You do have to be a dyed-in-the-wool geek to even come close to caring about what I am about to say. This is not terribly hard material if you have a little experience with web development — indeed, for some of you, this is probably old news, although I haven’t found very many resources on this exact topic. For most of the rest of the populace, however, this may all be greek to you. You have my reprieve if you want to stop reading now and go update your Facebook profile. No worries.

In this article, there will be some CommandLine Fu. You’ve been warned. Get Mr. Terminal out and dust him off. You will need him.

Unlike some people, I personally don’t mind relying on MacPorts. I don’t often find myself in desperate need of the latest and greatest versions of open source software, and being a rather disorganized person, I find any package manager that handles dependencies for me to be a God send. For my purposes, MacPorts is just such a package manager. Of course, when I have to, I’ll compile from source manually in true haXor fashion. 99% of the time, however, MacPorts gets it done for me.

Before you even get to install MacPorts, though, you need to have Apple’s humongous XCode tools installed. Robby Russell of PLANET ARGON wrote up a great summary of getting your development environment in order in the first part of his latest article on installing Rails and PostgreSQL on Mac OS X (http://www.robbyonrails.com/articles/2008/01/22/installing-ruby-on-rails-and-postgresql-on-os-x-third-edition). Read and follow Phase One of that article, which covers XCode and MacPorts, and you’ll be all set to get started here with nginx and FastCGI. If you already have XCode and MacPorts set up and work­ing, skip ahead.

As an aside, you might even want to go all the way through Robby’s tutorial and install Ruby on Rails too — if you’re a web developer and you haven’t yet checked out this awesome web application framework, you owe it to yourself. It could very well change your life. You can pick up back here when you’re done.

Installing nginx

Accord­ing to the nginx English wiki:

Nginx was written by Igor Sysoev for rambler.ru, Russia’s second most visited website, where it has been running in production for over two and a half years. Igor has released the source code under a BSD-like license. Although still in beta, Nginx is known for its stability, rich feature set, simple configuration, and low resource consumption.

Are you ready to give it a spin? Assuming that you’re up and running with MacPorts, you are now ready to install the mighty nginx web server on your Mac. This really is the easy part of the process. Open up Terminal if it’s not already running, and type the following command at the prompt:

sudo port install nginx

This will instruct MacPorts to download the nginx source, compile it, and install it in /opt/local. When it is nearly finished, the script should inform you that there is a property list you need to load via launchctl that will enable nginx to start on system reboot:

###########################################################
# A startup item has been generated that will aid in
# starting nginx with launchd. It is disabled
# by default. Execute the following command to start it,
# and to cause it to launch at startup:
#
# sudo launchctl load -w /Library/LaunchDaemons/org.macports.nginx.plist
###########################################################

If you want nginx to launch at startup, execute that command once the installation completes:

sudo launchctl load -w /Library/LaunchDaemons/org.macports.nginx.plist

Otherwise, you can run the nginx daemon manually (but wait a moment before doing so):

sudo /opt/local/sbin/nginx

Finally, before you can run nginx, you need to create a working default nginx configuration file, thusly:

sudo cp /opt/local/etc/nginx/nginx.conf.default \\
        /opt/local/etc/nginx/nginx.conf

This file as is should be sufficient to get you started. There are a number of resources available for tailoring the nginx configuration to your needs. I’ve listed a few of them at the end of this tutorial, but as I’m not an expert on nginx configuration, I’ll let them do the explaining, and just mumble something like, “that’s outside the scope of this article,” or some such. Gotta fight that scope creep!

Just kidding. I’ll list some example config files at the end of this article, after I cover how to install PHP. That’s next.

Installing PHP with FastCGI

PHP, the way we have to run it, is a bit tricky to install. We have to run PHP as a CGI daemon independently of nginx, because unlike Apache, nginx doesn’t have anything like a mod_php that we can simply plug in. Despite that complication, the performance benefits of nginx still out strip the many conveniences of bloated Apache, at least in my view.

If you’re running Leopard, you technically already have PHP installed (This could also be the case with Mac OS X 10.4, or Tiger). However, just so we can know exactly (well, roughly) what we’re getting, we can just install a separate instance of PHP with MacPorts. Because MacPorts installs everything into /opt/local, and the existing PHP is installed in /usr/bin, never the twain shall meet. They’ll never know about each other. No marital infidelity jokes, now. Run this:

sudo port install php5 +fastcgi fcgi

MacPorts will again run off and download/compile/install a whole slew of dependencies, eventually getting around to PHP with FastCGI. If we’d compiled from source, I probably would have forgotten to tell you about one or two of them, and you’d be screwed. I like MacPorts because I am lazy and forgetful.

The installation script of PHP doesn’t provide you with a property list to load via launchctl, so we’ll have to roll our own. Create a new text file somewhere, name it whatever you like (I called mine com.tudorstudio.php-fastcgi.plist), and copy this in:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Debug</key>
  <false/>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PHP_FCGI_CHILDREN</key>
    <string>2</string>
    <key>PHP_FCGI_MAX_REQUESTS</key>
    <string>1000</string>
  </dict>
  <key>Label</key>
  <string>com.tudorstudio.phpfcgi</string>
  <key>OnDemand</key>
  <false/>
  <key>ProgramArguments</key>
  <array>
    <string>/opt/local/bin/php-cgi</string>
    <string>-b 127.0.0.1:9000</string>
    <string>-q</string>
  </array>
  <key>RunAtLoad</key>
  <false/>
</dict>
</plist>

As configured above, the daemon will run on port 9000, and spawn 2 child processes. Feel free to change those values to fit your needs. Notice that the filename (com.tudorstudio.phpfcgi in the above example) is referenced on about line 15; I’d recommend that you change that to whatever you named your own PLIST. You are free to use my filename if you like.

Now, in the same directory as your newly created property list, run the command to load it into your launchd configuration, like so (again, changing the filename to match your file):

sudo launchctl load -w com.tudorstudio.php-fastcgi.plist

This tells launchd to launch our FastCGI daemon process at system startup. If you wish, you can run this daemon manually:

sudo /opt/local/bin/php-cgi -q -b 127.0.0.1:9000

There is also a shell script that can simplify the command for you — it’s listed at the bottom.

Config Files

As promised, here are some example configuration files for nginx on the Mac. These are pretty close to what I’m currently using, but I don’t claim to be very knowledgeable in this area — it’s just what works for me, and I’m sure I’m just barely scratching the surface. All three of these config files are located in /opt/local/etc/nginx.

nginx.conf

# user nobody;
worker_processes  1;

error_log  /opt/local/var/log/nginx/error.log;
pid        /opt/local/var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /opt/local/etc/nginx/mime.types;
    default_type  application/octet-stream;

    access_log  /opt/local/var/log/nginx/access.log;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;
    tcp_nodelay        on;

    gzip  on;

    include /opt/local/etc/nginx/sites.conf;
}

sites.conf

# You may add here your
# server {
#       ...
# }
# statements for each of your virtual hosts

server {
    listen 80;
    server_name localhost;
    # server_name example.com;

    access_log  /opt/local/var/log/nginx/localhost.access.log;

    location / {
        root   /opt/local/share/nginx/html;
        index  index.php index.html index.htm;
    }

    #error_page  404  /404.html;

    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /opt/local/share/nginx/html;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    location ~ \\.php$ {
        fastcgi_pass  127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME /opt/local/share/nginx/html$fastcgi_script_name;
        include       /opt/local/etc/nginx/fastcgi.conf;
    }
}

fastcgi.conf

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  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;

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;

php_fastcgi.sh

As promised, here is an example of a bash script that can be used to simplify the spawning of the PHP-CGI daemon. Please use responsibly, and alter it as you see fit.

#!/bin/bash
# From http://blog.kovyrin.net/2006/05/30/nginx-php-fastcgi-howto/
# Modified per http://henrik.nyh.se/2008/02/php-in-nginx-on-os-x.
# Further modified by Stephen Tudor for private use.

## ABSOLUTE path to the PHP binary
PHPFCGI="/opt/local/bin/php-cgi"

## tcp-port to bind on
FCGIPORT="9000"

## IP to bind on
FCGIADDR="127.0.0.1"

## number of PHP children to spawn
PHP_FCGI_CHILDREN=2

## number of request before php-process will be restarted
PHP_FCGI_MAX_REQUESTS=1000

# allowed environment variables sperated by spaces
ALLOWED_ENV="PATH USER"

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

################## no config below this line

ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_CHILDREN"
ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS"
ALLOWED_ENV="$ALLOWED_ENV FCGI_WEB_SERVER_ADDRS"

if test x$UID = x0; then
  EX="/usr/bin/sudo -u $USERID $PHPFCGI -q -b \\"$FCGIADDR:$FCGIPORT\\""
else
  EX="$PHPFCGI -b $FCGIADDR:$FCGIPORT"
fi

echo $EX

# copy the allowed environment variables
E=

for i in $ALLOWED_ENV; do
  E="$E $i=${!i}"
done

# clean environment and set up a new one
nohup env - $E sh -c "$EX" &> /dev/null &

Conclusion

I hope that you found this article useful in getting you on your way with nginx and PHP with FastCGI on Mac OS X. I have no reason to believe this wouldn’t work just as well with Tiger as it does Leopard, but since Leopard is what I’ve got to work with, YMMV.

Attribution

For the most authoritative, albeit terse, information on nginx in English, head on over to the nginx English wiki and drink in as much as you can.

For the majority of what I included here in this little exercise, I owe beer to:

  • Henrik Nyh, who wrote one of the only Mac/nginx/php articles I could find, and put up a pastie of an example FCGI shell script.
  • Matthew King, who posted the original snippet of an example launchd property list for PHP with FCGI.
  • Chu Yeow, who listed some awesome nginx config file examples.
  • Alexei Kovyrin, who posted a nice nginx/PHP/FCGI howto.

Thanks, guys, for being awesome.