A Portable Apache+PHP Environment for Debian/Ubuntu and MacOS X (aka Dynamic vhosts made Easy)

What You Need

  • A computer with Ubuntu or MacOS X installed and working; tested on Ubuntu 9.10+ and OS X 10.8
  • An Internet Connection


We are going to setup a new, portable Apache server instance that listens on all of your computer's IP addresses and maps the hostnames being requested from that computer to directories on your hard disk. This means that you can add websites without restarting or reconfiguring Apache in any way. You just create a new directory and Apache figures it out for you. Brilliant, no?

What do I mean by portable? I mean every configuration file, content and scripts you need to run that entire Apache instance is in one folder that is not part of the operating system. You can copy that folder to any other box and execute the script to start it without any modifications of any kind. All you need is to install Apache2, PHP5 and some other modules. You aren't even using the system provided init scripts.

Preliminary Setup

You need to install Apache, PHP and xdebug and then configure things.  

MacOS X 10.8

OS X 10.8 comes preloaded with Apache 2.2 and PHP 5.3.  It is disabled by default which is what you want.  If you have enabled it manually please run the following from a terminal (Applications->Utilities->Terminal):

  • sudo apachectl stop 


Open a terminal (Applications->Accessories->Terminal) and run these commands in this order:

  • sudo apt-get install php5 libapache2-mod-php5 php5-xdebug # Install Apache/PHP
  • sudo update-rc.d -f apache2 remove # Disable Apache Service
  • sudo /etc/init.d/apache2 stop # Stop Apache Service
  • sudo a2enmod php5 # Enable PHP5
  • sudo a2enmod vhost_alias # Enable Virtualhost Aliasing
  • sudo a2enmod rewrite # Enable rewrite module for Zend Framework, etc

Environment Preparation

Now that we have installed, configured and disabled the system-wide Apache instance we need to extract my handy Apache environment and set it up.

Open a terminal (Applications -> Accessories -> Terminal) and run the following commands:

  • # Download the file to your home, extract it to /srv and name it something handy
  • cd
  • curl -O http://www.webaugur.com/wares/files/apache2-portable-latest.tar.gz
    Or Browse other versions of Apache2 Portable
  • # On MacOS X you may need to create /srv: 
  • sudo mkdir /srv
  • pushd /srv
  • sudo tar xvzf ~/apache2-portable-latest.tar.gz
  • sudo mv apache2-portable* development
  • pushd development
  • # Run this script as whichever user account is going to be developing code
  • bin/fixperms
  • # start the service
  • sudo etc/init.d/apache2 start
  • # this will create links so the service starts at boot (not yet working on OS X)
  • sudo bin/statuscheck

You should now be able to open http://localhost/ in your browser and see your phpinfo() output.

Adding Content

I have prepopulated a simple test script into www/localhost/public/index.php so you can see if its working right away.

The way it works is very simple. If you point a hostname (either in DNS or in your /etc/hosts file) to any of your computer's IP addresses Apache will serve up content from www/hostname/public. So, lets say you point www.ilikeapache.foo to your computer. You will place your content into www/www.ilikeapache.foo/public for Apache to find it. You can symlink many hostnames to a common directory as needed. You might create a link called www/ilikeapache.foo that points to www.ilikeapache.foo, for example. Thus serving up the same content for both host names. Much less ugly than slaving over hot config files into the wee hours of the morning. Simply amazing if you have hundreds of hosts pointing to one server.

What you Get

Here's where you may get a little lost if you're new to all of this. And I'll show you how to use some of this stuff in future articles. So just hang tight if you're not sure how all of this works.

Here's a breakdown of useful directories and files in the default config:

  • bin/statuscheck - Cron job to (re)create rc2.d symlink and restart a failed apache.  Run every few minutes from cron.
  • var/log/apache2/access_log - Access log with hostname in the first column
  • var/log/apache2/error_log - Regular error log file
  • var/log/php.log - PHP Error Log (tail -f var/log/php.log to see errors in realtime)
  • var/log/xdebug - PHP xdebug stack traces and cachegrind (memory profiling) dumps
  • var/spool/mail/ - All outgoing email is intercepted (using bin/mailtrap as the "sendmail" program) and dropped here, one email per (timestamped) file. Files are stored in a format that can be piped directly into sendmail for real delivery.
  • lib/cgi-bin - If you create this directory it will be a common /cgi-bin for all hostnames on this Apache instance.
  • etc/apache2/include/php5-development.conf - The default PHP configuration intercepts email, sets up xdebug and enables error logging.
  • etc/apache2/include/php5-production.conf - Alternate PHP configuration for production servers disables all debugging features.  You should create a site-specific vhost configuration to use this config.
  • etc/apache2/sites-available/mass-vhost.conf - Default mass vhost config which maps the HTTP Host header to directories in www.  This configuration sets the "development" application environment variable for Zend Framework.  It also sets the include path for Zend Framework to be in the opt/ZendFramework folder of this service.  Symlink this file as sites-enabled/00-mass-vhost.conf to enable this as your fallback vhost.
  • etc/apache2/sites-available/svn.conf - Site-specific vhost example for a Subversion repository.  Symlink this as sites-enabled/01-svn.conf to enable this for the domain (ServerName, ServerAlias) specified in the file.
  • etc/apache2/run - PID and lock files for this Apache instance

Why Oh Why

Some questions and answers I expect you'll have.

  1. Q: Why do I place content in a hostname/public subdirectory?
    A: You should never put your application code in a public directory. Zend Framework uses the convention of having a directory named "public" that maps URLs into your application code. I use ZF quite a lot and therefore followed this convention.
  2. Q: Can I run more than one instance of Apache like this?
    A: Absolutely, this is why I wrote my own init script. You can have as many Apache instances as you have IP addresses to give them. Modify etc/apache2/sites-available/mass-vhost.conf to Listen on a specific IP address instead of all IPs. You can have one instance on multiple IPs, as well. Just make sure none of the instances are listening on all IPs (or each other's IPs) or you'll have port conflicts.
  3. Q: How do I move my code and apache instance to another server?
    A: Run the setup and preparation steps above on the new host and instead of downloading my copy of the apache-portable just copy your entire directory containing your instance and content from /srv on the current machine to /srv on the new machine.

Troubleshooting and Corrections

If you run into troubles check the var/log/apache2/error_log for relevant problems. If you get completely stumped maybe I missed a step. Feel free to email me with a description of your problem and I'll try to take a look.

Common Problems

  • DOCUMENT_ROOT is not set by Apache vhost_alias and a lot of badly written PHP code relies on it.  I suggest you fix the code to work right.
    Replace usage of that variable with something like realpath( dirname( __FILE__) ) in your bootstrap code.
    As a really hideously ugly hack you can redefine it $_SERVER['DOCUMENT_ROOT'] = realpath( dirname( __FILE__) );