Multiple PHP versions with Apache using FastCGI on OS X

Like many others, I was quite excited to see PHP 5.3 come out and immediately wanted to start using it. Unfortunately reality set in in the form of legacy sites running 5.2 (or older) and the need to maintain them. My solution is to have multiple versions of PHP running which can be configured on a per virtual host basis, specifically PHP 5.2.10 & 5.3 with Apache 2.2.12 and MySQL 5.1.36. I compiled the software on a Mac, but the instructions should work with little or no modification on most *nix flavors.

Disclaimer: This is what my dev setup is on my local machines (MBP & Mac Pro) and what works for me. I make no guarantees that this will work for you or that you won’t ruin your OS doing this.

Prerequisites

Since we are compiling from source, we’re gonna need gcc and make among other programs. They can all be downloaded for free with the Apple Developer Tools.

I like putting what I compile in a parent directory “/opt” for clarity and to not affect any built in OS X files. To do this:

sudo mkdir -p /opt/src
sudo chmod g+w /opt/src
cd /opt/src

Wget is going to be used to retrieve tarballs without having to leave the command line. It’s quite simple to install and will get your feet wet if you’ve never compiled software from source before. To install wget, you’re gonna have to download via browser the first time.

Download: http://ftp.gnu.org/gnu/wget/wget-latest.tar.bz2

Then in Terminal:

cp ~/Downloads/wget-latest.tar.bz2 .
tar xvjf wget-latest.tar.bz2
cd wget-1.11.4/
./configure --prefix=/opt
make
sudo make install

Remaining steps from here will be done via Terminal.

Now we want to add /opt/bin to our PATH so we don’t have to type it out every time. Open up ‘~/.profile’ with your favorite text editor *cough*VI*cough* and prepend it to the variable. In my case it looks like this:

export PATH="/opt/bin:$PATH"

Finally source the file to apply the changes:

. ~/.profile

You only have to this once. Next time you open Terminal it will be sourced automatically.

Still with me? Then on to…

Installing MySQL

Download and install:

cd /opt/src
wget http://dev.mysql.com/get/Downloads/MySQL-5.1/mysql-5.1.36.tar.gz/from/http://mysql.he.net/
tar xvzf mysql-5.1.36.tar.gz
cd mysql-5.1.36
CC=gcc CFLAGS="-O3 -fno-omit-frame-pointer" CXX=gcc \
CXXFLAGS="-O3 -fno-omit-frame-pointer -felide-constructors -fno-exceptions -fno-rtti" \
./configure --prefix=/opt/mysql \
--localstatedir=/opt/mysql/data \
--with-extra-charsets=complex \
--enable-thread-safe-client \
--enable-local-infile \
--enable-assembler \
--with-mysqld-ldflags=-all-static \
--with-plugins=innobase
make
sudo make install

Create some symlinks from /opt/bin to MySQL’s bin:

sudo ln -s  /opt/mysql/bin/* /opt/bin/.
Initialize the system tables and run it for the first time:
sudo mysql_install_db --user=mysql
sudo mysqld_safe --user=mysql --log &

SECURE THE ROOT ACCOUNT AND REMOVE ANONYMOUS ACCESS:

mysql -u root
mysql> delete from mysql.user where User != 'root';
mysql> update mysql.user set Password=PASSWORD('supersecretpassword') where User='root';
mysql> flush privileges;
mysql> exit

Now you should need the password to connect as root, and anonymous access should be disabled.

Installing Apache

Download and install:

cd /opt/src
wget http://apache.mirror.facebook.net/httpd/httpd-2.2.12.tar.bz2
tar xvjf httpd-2.2.12.tar.bz2
cd httpd-2.2.12
./configure --prefix=/opt/apache2 \
--enable-mods-shared=all \
--enable-ssl \
--with-mpm=worker \
--enable-suexec \
--with-suexec-bin=/opt/apache2/bin \
--with-suexec-caller=_www \
--with-suexec-docroot=/projects
make
sudo make install
sudo ln -s /opt/apache2/bin/* /opt/bin/.

We’ll come back to the Apache config after installing PHP.

Installing mod_fastcgi

Download and install:

cd /opt/src
wget http://www.fastcgi.com/dist/mod_fastcgi-SNAP-0811090952.tar.gz
tar xvzf mod_fastcgi-SNAP-0811090952.tar.gz
cd mod_fastcgi-SNAP-0811090952.tar.gz
cp Makefile.AP2 Makefile
make top_dir=/opt/apache2
sudo make install top_dir=/opt/apache2

Create a tmp folder for FastCGI:

sudo mkdir -p /opt/tmp/fcgi
sudo chmod -R 0777 /opt/tmp/fcgi

Create the config file for mod_fastcgi at “/opt/apache2/conf/extra/httpd-fastcgi.conf”:

LoadModule fastcgi_module modules/mod_fastcgi.so
FastCgiIpcDir /opt/tmp/fcgi
AddHandler fastcgi-script .fcgi
FastCgiConfig -autoUpdate -singleThreshold 100 -killInterval 300
AddType application/x-httpd-php .php
ScriptAlias /fastcgi/ /opt/apache2/cgi-bin/
<Directory "/opt/apache2/cgi-bin">
 Options ExecCGI
 SetHandler fastcgi-script
 Order allow,deny
 Allow from all
</Directory>

Install PHP 5.2.10

Download and install:

cd /opt/src
wget http://us.php.net/get/php-5.2.10.tar.bz2/from/us2.php.net/mirror
tar xvjf php-5.2.10.tar.bz2
cd php-5.2.10
./configure --prefix=/opt/php5.2 \
--with-config-file-path=/opt/php5.2 \
--with-mysqli=/opt/bin/mysql_config \
--with-mysql=/opt/mysql \
--with-curl \
--enable-cli \
--enable-fastcgi \
--enable-discard-path \
--enable-force-cgi-redirect
make
sudo make install

Copy php.ini to your install dir:

sudo cp /opt/src/php-5.2.10/php.ini-dist /opt/php5.2/php.ini

Optional, but highly recommended. Open php.ini, change error_reporting to strict and turn magic quotes off:

error_reporting = E_ALL | E_STRICT
magic_quotes_gpc = Off

Finally, create a PHP fcgi “wrapper” script at “/opt/apache2/cgi-bin/php52.fcgi” (you will have to use sudo):

#!/bin/sh
PHP_FCGI_CHILDREN=1
export PHP_FCGI_CHILDREN
PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_MAX_REQUESTS
exec /opt/php5.2/bin/php-cgi

Set the wrapper to be executable:

sudo chmod +x /opt/apache2/cgi-bin/php52.fcgi

Installing PHP 5.3

Download and install:

cd /opt/src
wget http://us3.php.net/get/php-5.3.0.tar.bz2/from/us2.php.net/mirror
tar xvjf php-5.3.0.tar.bz2
cd php-5.3.0
./configure --prefix=/opt/php5.3 \
--with-config-file-path=/opt/php5.3 \
--with-curl \
--enable-cli \
--with-mysql=mysqlnd \
--with-mysqli=mysqlnd \
--with-pdo-mysql=mysqlnd
make
sudo make install

Copy over php.ini:

sudo cp /opt/src/php-5.3.0/php.ini-development /opt/php5.3/php.ini

And create the wrapped like we did for 5.2, but with a different name “/opt/apache2/cgi-bin/php53.fcgi”:

#!/bin/sh
PHP_FCGI_CHILDREN=1
export PHP_FCGI_CHILDREN
PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_MAX_REQUESTS
exec /opt/php5.3/bin/php-cgi

Lastly, make it executable:

sudo chmod +x /opt/apache2/cgi-bin/php53.fcgi

Tying it all together

Now we’ve got our MAMP2 stack ready to go, just need to setup our Apache config to use it.

Open up “/opt/apache2/conf/httpd.conf” and make the following changes:

  • Change “User” and “Group” to “_www”
  • Add index.php to “DirectoryIndex”. Should look something like “DirectoryIndex index.php index.html”
  • Add permissions to your docroot. (I keep all my sites under “/projects”, just replace that with whatever you use, i.e. “/var/www”, “~/sites”):
    <Directory "/projects">
     Options Indexes FollowSymLinks
     AllowOverride All
     Order allow,deny
     Allow from all
    </Directory>
  • Uncomment “Include conf/extra/httpd-vhosts.conf”
  • Add “Include conf/extra/httpd-fastcgi.conf”

Set up virtual hosts. In “/opt/apache2/conf/extra/httpd-vhosts.conf”, delete everything below the “NameVirtualHost” directive and add:

<VirtualHost *:80>
 ServerAdmin you@domain.com
 DocumentRoot "/projects/site1"
 ServerName site1
 ErrorLog "logs/site1-error_log"
 CustomLog "logs/site1-access_log" common
 Action  application/x-httpd-php /fastcgi/php52.fcgi
</VirtualHost>
<VirtualHost *:80>
 ServerAdmin you@domain.com
 DocumentRoot "/projects/site2"
 ServerName site2
 ErrorLog "logs/site2-error_log"
 CustomLog "logs/site2-access_log" common
 Action  application/x-httpd-php /fastcgi/php53.fcgi
</VirtualHost>

For testing purposes, let’s set up a simple PHP file in each VHost. For “site1″ the path to the file will be “/projects/site1/index.php” and look like this:

<h2>Site 1</h2>
<?php
phpinfo();
?>

For “site2″ it will be at “/projects/site2/index.php” and look like this:

<h2>Site 2</h2>
<?php
phpinfo();
?>

The last step is to set up your hosts file (sudo vim /etc/hosts) so you can get to the sites. Add the following entry

127.0.0.1 site1 site2

Now fire up your favorite browser and point it at “http://site1″ and “http://site2″ respectively. You should see something like this:

PHP 5.2 and 5.3 side by side

PHP 5.2 and 5.3 side by side

That’s it!

Please respond in the comments if you find any glaring errors or security issues, or just to say this helped!

Next post will be how to set up MySQL and Apache to start automatically via launchd.

References:

http://dev.mysql.com/doc/refman/5.1/en/index.html
http://httpd.apache.org/docs/2.2/
http://www.php.net/manual/en/
http://www.fastcgi.com/docs/faq.html
http://timdorr.com/archives/2006/02/php4-and-php4-s.php

Share and Enjoy:
  • E-mail this story to a friend!
  • Digg
  • del.icio.us
  • Facebook
  • Twitter

Tags: , , , ,

7 Responses to “Multiple PHP versions with Apache using FastCGI on OS X”

  1. Joe Devon says:

    Very nicely done Robert. BTW, have you ever thought of switching out apache for lighttpd or nginx? One of these days I want to try them out on an Amazon instance. Supposed to be WAAY faster than apache.

    • Robert says:

      I haven’t used Nginx, but I have used Lighty quite a bit. I think the advantage of using Lighty over Apache is that it’s smaller and faster for serving up static files. As far as FastCGI is concerned, I personally haven’t seen a huge difference in performance.

      In all honesty, I don’t really prefer one over the other. They both work great, I tend to stick to Apache though because it is more widely used and sometimes you don’t get to choose what the http server is.

  2. Darin says:

    This is a great idea, and it really helped me along, but I’m having a problem I can’t solve. I’m running the latest version of Leopard on a Mac Mini. For one particular webmail application, I need to drop back to PHP 5.2 (installed at the usual /usr/bin/php). For everything else I’m using PHP 5.3 (installed in /opt/local/bin/php).

    To force the redirect for the /mail subdirectory, I created a .htaccess file containing the following:

    AddHandler x-httpd-php php
    Action x-httpd-php /usr/bin/php

    The server is actually picking it up as expected, but it seems to be doing the wrong thing with it. Rather than executing “/usr/bin/php mail/index.php” (note the space between “php” and “mail”, it’s instead executing “/usr/bin/php/mail/index.php” — jamming everything together into a single string and trying to execute that. Since the path “/usr/bin/php/mail/index.php” doesn’t exist, I’m getting a 404.

    Any ideas?

    • Robert says:

      It’s really hard to say without knowing more about the setup or which app it is. The first place I would check is the webmail script that executes the command.

  3. [...] found this one after I wrote up this tutorial at http://cuadradevelopment.com. It’s a bit different, but should work as [...]

  4. Fred says:

    Great write up. Thanks!

    I only have one strange problem – the full php configuration I used does not show up, only a partial configuration appears.

    FYI, I’m using Snow Leopard’s build in Apache, for simplicity, php 5.2.12 and php 5.3.1.

    After I comment out the php make install LoadModule php5_module in httpd.conf, in the result phpinfo() output I get (php 5.3.1):

    Configure Command ‘./configure’ ‘–prefix=/opt/php5.3′ ‘–with-config-file-path=/opt/php5.3′ ‘–with-curl’ ‘–enable-cli’ ‘–with-mysql=mysqlnd’ ‘–with-mysqli=mysqlnd’ ‘–with-pdo-mysql=mysqlnd’

    However if I reactive LoadModule php5_module in httpd.conf and restart the server (forcing the direct use of php) I get:

    Command ‘./configure’ ‘–prefix=/opt/php5.3′ ‘–with-config-file-path=/opt/php5.3′ ‘–with-curl’ ‘–enable-cli’ ‘–with-mysql=mysqlnd’ ‘–with-mysqli=mysqlnd’ ‘–with-pdo-mysql=mysqlnd’ ‘–with-zlib’ ‘–with-zlib-dir=/usr’ ‘–with-apxs2=/usr/sbin/apxs’ ‘–with-ldap=/usr’ ‘–with-iodbc=/usr’ ‘–with-curl=/usr’ ‘–with-mysql-sock=/tmp’ ‘–with-openssl=/usr’ ‘–with-xmlrpc’ ‘–with-pear’ ‘–with-iconv-dir=/usr/local’ ‘–with-gd’ ‘–with-jpeg-dir=/usr/local/lib’ ‘–with-png-dir=/usr/X11R6′ ‘–with-freetype-dir=/usr/X11R6′ ‘–with-xpm-dir=/usr/X11R6′ ‘–with-sqlite’ ‘–with-pdo-sqlite’ ‘–with-ldap=/usr’ ‘–with-ldap-sasl=/usr’ ‘–with-bz2=/usr’ ‘–enable-exif’ ‘–enable-ftp’ ‘–enable-sockets’ ‘–enable-mbstring’ ‘–enable-mbregex’ ‘–enable-soap’ ‘–enable-bcmath’ ‘–enable-dba’ ‘–enable-gd-native-ttf’

    The result is roughly the same for php 5.2.12.

    No errors in the error log. Just missing most of my php configuration, which, as you can imagine, hamstrings the application!

    Any ideas?

  5. I like your weblog very much. Will bookmark. Keep up to briliant posting on it. Thank you

Leave a Reply