Archive for August, 2009

Multiple PHP versions with Apache using FastCGI on OS X

Friday, August 7th, 2009

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

Ready. Set. Go!

Tuesday, August 4th, 2009

Moved the blog over from my personal site to the biz site, and I intend to post more often :)