alanthing

Linux and other things

Speed Up PHP on NFS With Turbo_realpath

If you run a website based on PHP, and have your source files on a network file system like NFS, OCFS2, or GlusterFS, and combine it with PHP's open_basedir protection, you'll quickly notice that the performance will degrade substantially. Normally, PHP can cache various path locations it learns after processing include_once and require_once calls via the realpath_cache. There's a bug in PHP that effectively disables the realpath_cache entirely when combined with open_basedir. Popular PHP applications with Drupal and WordPress make heavy use of these functions to include other files, so you would very quickly notice the drop in performance in this scenario. If you want to isolate your websites from each other (or from the rest of the operating system), how can you retain any shred of performance?

This is where Artur Graniszewski's turbo_realpath extension really comes in handy. I won't retype his installation instructions, so follow the previous link to get it installed manually.

If you're running CentOS 5 or CentOS 6, check out yum.echoditto.com and you'll find source and compiled RPMs that will install alongside the RedHat/CentOS-supplied PHP packages. The RPM will create a basic configuration file at /etc/php.d/turbo_realpath.ini. Essentially, it enables the PHP module but defaults all settings off, so you will need to read the comments (taken from Artur's most recent post on turbo_realpath) to determine how you want to use it.

Configuration

We frequently use turbo_realpath on a per-VirtualHost basis with Apache 2.2 and mod_php. If you use PHP-FPM, you can apply similar settings in your FPM pool configuration files. If you install our RPM and don't edit /etc/php.d/turbo_realpath.ini, add something similar to the following to each VirtualHost:

1
2
3
<IfModule php5_module>
  php_admin_value realpath_cache_basedir "/var/www/vhosts/domain.com:/usr/share/pear:/usr/share/php:/usr/lib64/php:/usr/lib/php:/tmp:/var/tmp"
</IfModule>

This is effectively the same using open_basedir; any directories referenced in realpath_cache_basedir will be the only ones the website is allowed to access, and they will be cached as determined by the realpath_cache_size and realpath_cache_ttl. If you look in php.ini, you may notice the default values for these are:

1
2
3
4
5
6
7
; Determines the size of the realpath cache to be used by PHP. This value should
; http://www.php.net/manual/en/ini.core.php#ini.realpath-cache-size
realpath_cache_size = 16k

; Duration of time, in seconds for which to cache realpath information for a given
; http://www.php.net/manual/en/ini.core.php#ini.realpath-cache-ttl
realpath_cache_ttl = 120

You may want to increase these if you're finding your website is still not loading quickly. On our systems, we have bumped the realpath_cache_size and realpath_cache_ttl settings up to 1m and 300, respectively.

Speed and Security!

With turbo_realpath enabled, realpath_cache_basedir set to appropriate open_basedir-like values, and realpath_cache_size and realpath_cache_ttl increased from defaults, we're able to have isolated PHP sites and have better performance by caching the locations of included/required files effectively. Hopefully, our RPMs will help you on your system for a quick installation of the excellent turbo_realpath module!

References

VirtualBox Extension Pack With One Line

Just installed VirtualBox on OS X and need the Extension Pack? This (really long) one-liner will download and install it for you. Run in terminal and enter your password once and you're off to the races!

1
export version=$(/usr/bin/vboxmanage -v) && export var1=$(echo ${version} | cut -d 'r' -f 1) && export var2=$(echo ${version} | cut -d 'r' -f 2) && export file="Oracle_VM_VirtualBox_Extension_Pack-${var1}-${var2}.vbox-extpack" && curl --silent --location http://download.virtualbox.org/virtualbox/${var1}/${file} -o ~/Downloads/${file} && VBoxManage extpack install ~/Downloads/${file} --replace && rm ~/Downloads/${file} && unset version var1 var2 file

Note that VirtualBox must be installed first :)

“Real-time” Ruby Gem Development

Recently, I've been working on adding functionality for Storm on Demand's API to the excellent fog library. There didn't seem to be a clear way, that I could find, to work on a Ruby gem in real-time, meaning I save the file in my editor and immediately run some code. Most guides I found tended to advise to create the gem file and install it. Stephen Ball demostrates the use of bundle console which drops you into irb for testing, but I was looking for a way to test my own scripts outside of a shell with my recently-saved gem files.

Thankfully this isn't too difficult to set up and this will allow you to work on your gem in "real-time;" save and test, without bundle console or installing lots of dev gems.

I should note two things here: I'm not a Ruby developer, and I've really only tested this thoroughly with fog, no others. If this ends up being a terrible way to do things, please let me know.

I'm using RVM to keep my gem development isolated from the rest of my system. If you haven't already, set up rvm on your system. If you're using rbenv instead, use applicable commands.

You'll obviously need to start in a gem development directory. In my case, I start with fog:

1
2
git clone git@github.com:fog/fog.git
cd fog

I'm going to switch to ruby 1.9.3 and use a new gemset called fogdev:

1
rvm use 1.9.3 && rvm gemset create fogdev && rvm use 1.9.3@fogdev

You may want to create a .rvmrc file so you don't have to use rvm use constantly.

Install the prerequesite gems as listed in the gemspec file with bundler:

1
bundle install

I'm going to grab the version number of fog from the gemspec file so I can reuse it later (if there are multiple '.gemspec' files in your directory, change '*.gemspec' to 'name.gemspec' as appropriate):

1
export GEMVERSION=$(grep -E '^[ \t]+s.version' *.gemspec | awk '{print $3}' | sed 's/'\''//g')

Now at this point, you can build and install the gem with rake build && gem install pkg/fog-${GEMVERSION}.gem or gem build or any other tools as most tutorials advise, but then whenever you edit the source files you'll have to repeat. Let's create some symbolic links instead. Start by going to the rvm gemset folder (this may be a different location for your environment):

1
cd ~/.rvm/gems/ruby-1.9.3-p286\@fogdev/

It seems as though the earlier bundle install command created the file bin/fog for me, but it doesn't work without either installing a gem or running the remaining commands below. Particularly for fog, if this bin/fog file is missing and you need it, you can grab it with the following:

1
[ ! -f bin/fog ] && curl --silent --output bin/fog https://gist.github.com/alanthing/5004411/raw/13f748f1cc19df7511ea2a01de6824eac3358905/fog && chmod +x bin/fog

Let's add our development gem with symbolic links (again, change as appropriate for your gem):

1
2
ln -s ~/fog/fog.gemspec specifications/fog-${GEMVERSION}.gemspec
ln -s ~/fog gems/fog-${GEMVERSION}

Now if you run gem list you should see your gem listed:

1
2
3
4
5
6
7
8
$ gem list
...snip...
ffi (1.0.11)
fission (0.4.0)
fog (1.9.0)
formatador (0.2.4)
jekyll (0.12.0)
...snip...

And, in my case, running fog uses the gemset file bin/fog and drops me into a fog-ised irb:

1
2
3
4
$ fog
  Welcome to fog interactive!
  :default provides VirtualBox and Vmfusion
>> 

If you see any mistakes or have any questions, let me know!

Usable MAMP on OS X 10.8 Mountain Lion

Install MAMP

Download the latest MAMP from http://mamp.info/en/downloads/index.html and run the installer.

Choose APC Cache

Open the MAMP app, open Preferences…, click the PHP tab, and change Cache to APC. Click OK to close Preferences and Quit MAMP.

MAMP Screenshot

PATH variable

Put MAMP binaries, including PHP, in the front of your $PATH (this is a single command):

1
echo 'export PATH="/Applications/MAMP/bin:/Applications/MAMP/Library/bin:$(find /Applications/MAMP/bin/php -type d -name "php5.4*" | sort | tail -1)/bin:$PATH"' >> ~/.bash_profile && source ~/.bash_profile

MySQL

Set up MySQL and stop it (default MySQL password is root, you do not have to change it when running mysql_secure_installation, though I recommend all of the other defaults):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cp -va /Applications/MAMP/Library/support-files/my-small.cnf /Applications/MAMP/conf/my.cnf

sed -i '' "s/^\(max_allowed_packet =\) [0-9]*M/\1 1G/g" /Applications/MAMP/conf/my.cnf

egrep "^# Uncomment the following if you are using InnoDB tables$" /Applications/MAMP/conf/my.cnf &>/dev/null && sed -i '' "s/^\(# Uncomment the following if you are using InnoDB tables\)$/\1@innodb_file_per_table/; y/@/\n/; s/^#\(innodb_.*\)/\1/g" /Applications/MAMP/conf/my.cnf

/Applications/MAMP/bin/startMysql.sh &

# Secure MySQL setup:
/Applications/MAMP/Library/bin/mysql_secure_installation
# -- OR --
# Less secure MySQL setup:
#/Applications/MAMP/Library/bin/mysql -uroot -proot -e"DELETE FROM mysql.user WHERE User=''; DELETE FROM mysql.user WHERE User='root' AND Host!='localhost'; FLUSH PRIVILEGES;"

/Applications/MAMP/bin/stopMysql.sh

PHP

Set timezone, and increase timeouts and memory values:

1
2
3
sed -i '-default' "s|^;*\(date\.timezone[[:space:]]*=\).*|\1 \"`systemsetup -gettimezone|awk -F"\: " '{print $2}'`\"|; s|^\(memory_limit[[:space:]]*=\).*\(\;.*\)|\1 256M \2|; s|^\(post_max_size[[:space:]]*=\).*|\1 200M|; s|^\(upload_max_filesize[[:space:]]*=\).*|\1 100M|; s|^\(default_socket_timeout[[:space:]]*=\).*|\1 600|; s|^\(max_execution_time[[:space:]]*=\).*\(\;.*\)|\1 300 \2|; s|^\(max_input_time[[:space:]]*=\).*\(\;.*\)|\1 600 \2|;" $(find /Applications/MAMP/bin/php -type d -name "php5.4*" | sort | tail -1)/conf/php.ini

echo -e "\n[apc]\napc.shm_size = 192M\napc.rfc1867 = 1" >> $(find /Applications/MAMP/bin/php -type d -name "php5.4*" | sort | tail -1)/conf/php.ini

PECL Uploadprogress

Feel free to skip: This is quite involved when apc.rfc1867=1 does the job in most cases. This is mostly an exercise in building PHP modules for MAMP.

You'll need to install Xcode Command Line Tools from http://develop.apple.com/downloads in order to complete all of the steps below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
mkdir -p $(find /Applications/MAMP/bin/php -type d -name "php5.4*" | sort | tail -1)/include
cd $(find /Applications/MAMP/bin/php -type d -name "php5.4*" | sort | tail -1)/include
export PHPVER=$(cd /Applications/MAMP/bin/php && find * -type d -name "php5.4*" | sort | tail -1 | sed 's/php//') && curl -L -o php-${PHPVER}.tar.bz2 http://us3.php.net/get/php-${PHPVER}.tar.bz2/from/us3.php.net/mirror && unset PHPVER
ls php-5.4*bz2 | xargs -L1 tar jxpf && rm php-5.4*bz2
mv -v $(find * -type d -name "php-5.4*") php
cd php
./configure
cd ..

## Build automake and related tools
export build="$PWD/build"
mkdir -p $build
cd $build
curl -OL http://ftpmirror.gnu.org/autoconf/autoconf-2.68.tar.gz
tar xzf autoconf-2.68.tar.gz
cd autoconf-2.68
./configure --prefix=$build/autotools-bin
make
make install
export PATH=$PATH:$build/autotools-bin/bin
cd $build
curl -OL http://ftpmirror.gnu.org/automake/automake-1.11.tar.gz
tar xzf automake-1.11.tar.gz
cd automake-1.11
./configure --prefix=$build/autotools-bin
make
make install
cd $build
curl -OL http://ftpmirror.gnu.org/libtool/libtool-2.4.tar.gz
tar xzf libtool-2.4.tar.gz
cd libtool-2.4
./configure --prefix=$build/autotools-bin
make
make install
cd $build
unset build
rm autoconf-2.68.tar.gz automake-1.11.tar.gz libtool-2.4.tar.gz
rm -rf autoconf-2.68 automake-1.11 libtool-2.4
cd ..

pecl download uploadprogress
ls uploadprogress*z | xargs -L1 tar zxpf && rm -v uploadprogress*z package*xml
mv -v $(find * -type d -name "uploadprogress*") uploadprogress
cd uploadprogress
phpize
./configure --with-php-config=$(find /Applications/MAMP/bin/php -type d -name "php5.4*" | sort | tail -1)/bin/php-config
make
make install

echo -e "\n[uploadprogress]\nextension=uploadprogress.so" >> $(find /Applications/MAMP/bin/php -type d -name "php5.4*" | sort | tail -1)/conf/php.ini

Reference 1: http://www.lullabot.com/articles/installing-php-pear-and-pecl-extensions-on-mamp-mac-os-x-107-lion
Reference 2: http://jsdelfino.blogspot.com/2012/08/autoconf-and-automake-on-mac-os-x.html

Apache

Set up VirtualHosts in ~/Sites/mamp-vhosts.conf so it's easy to edit later:

1
2
3
4
5
cp -av /Applications/MAMP/conf/apache/httpd.conf{,-default}

export USERHOME=$(dscl . -read /Users/`whoami` NFSHomeDirectory | awk -F"\: " '{print $2}') && echo -e "\n# User VirtualHosts file (added after MAMP installer)\nInclude ${USERHOME}/Sites/mamp-vhosts.conf" >> /Applications/MAMP/conf/apache/httpd.conf && unset USERHOME

[ ! -d ~/Sites/logs ] && mkdir -pv ~/Sites/logs

IMPORTANT! Be sure to copy and paste the lines containing PORTNUM through the last EOF as a single command (this entire block is a single copy+paste):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
PORTNUM=$(egrep '^Listen [0-9]*' /Applications/MAMP/conf/apache/httpd.conf | awk '{print $2}') USERHOME=$(dscl . -read /Users/`whoami` NFSHomeDirectory | awk -F"\: " '{print $2}') cat > ~/Sites/mamp-vhosts.conf <<EOF
#
# Use name-based virtual hosting.
#
NameVirtualHost *:${PORTNUM}

#
# Set up permissions for VirtualHosts in ~/Sites
#
<Directory "${USERHOME}/Sites">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>

# For http://localhost in the MAMP default location
<VirtualHost _default_:${PORTNUM}>
    ServerName localhost
    DocumentRoot "/Applications/MAMP/htdocs"
</VirtualHost>

#
# VirtualHosts
#

## VirtualHost template
#<VirtualHost *:${PORTNUM}>
#  ServerName domain.local
#  CustomLog "${USERHOME}/Sites/logs/domain.local-access_log" combined
#  ErrorLog "${USERHOME}/Sites/logs/domain.local-error_log"
#  DocumentRoot "${USERHOME}/Sites/domain.local"
#</VirtualHost>

#
# Automatic VirtualHosts
# A directory at ${USERHOME}/Sites/webroot can be accessed at http://webroot.dev
# In Drupal, uncomment the line in .htaccess with: RewriteBase /
#
# This log format will display the per-virtual-host as the first field followed by a typical log line
LogFormat "%V %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combinedmassvhost
<VirtualHost *:${PORTNUM}>
  ServerName dev
  ServerAlias *.dev

  CustomLog "${USERHOME}/Sites/logs/dev-access_log" combinedmassvhost
  ErrorLog "${USERHOME}/Sites/logs/dev-error_log"

  VirtualDocumentRoot ${USERHOME}/Sites/%-2+
</VirtualHost>
EOF

If you read that closely, you'll see that it's set up to do MassVirtualHosts using the *.dev tld. For example, set up a web root at $HOME/Sites/project and you will be able to view it at http://project.dev:8888 without needing to create a separate VirtualHost. A custom log format is used, putting the domain name at the beginning of the line, so you can easily see the output as it related to a project.

Finishing Up

Open the MAMP application to Start Servers. Note that if you ever change the Apache port number, you'll need to edit ~/Sites/mamp-vhosts.conf appropriately.

As always with MAMP, you can work out of http://localhost:8888/ by putting files in /Applications/MAMP/htdocs. For example, the folder /Applications/MAMP/drupal will be accessible at http://localhost:8888/drupal. You can find other MAMP tools at http://localhost:8888/MAMP.

If you're having trouble connecting to MySQL with Sequel Pro or another utility, use /Applications/MAMP/tmp/mysql/mysql.sock as the socket path. If MAMP's MySQL is the only service running mysqld, this should not be necessary but YMMV…

Apache Modules on OS X With Homebrew

I have long been a big fan of Homebrew, a way of adding open-source software to OS X without much of the overhead and systems link MacPorts and Fink introduce. It's quickly become my favorite way to do PHP-based development on a Mac, and I've blogged before about various ways to integrate Homebrew-based software into your workflow.

In preparing new documentation for using Homebrew on 10.7 and 10.8 (coming soon to my EchoDitto blog), I wanted to find a way to run either mod_suexec or mod_fastcgi so I could use the built-in version of Apache without having to change folder ownership in random places where the web server needed to modify files. Homebrew did not have any formulas, but I found that Lifepillar had written formulas (mod_fastcgi) (mod_suexec) for them both and was hoping to get them into Homebrew.

Driven solely by my desire to have my documentation easier to follow, I set up a brew tap for these two Apache modules. Shortly after adding it to the list of interesting taps and branches, the decision was made to incorporate the repository under the Homebrew organization and have a place just for Apache modules.

As of today, here are the modules available in the tap:

If you have any problems or questions, let me know in the issue queue! Or write a formula and submit a pull request!

Three Ways to Get Drush on OS X

As I've said before, we love drush. It's hard to imagine doing Drupal work without it. If OS X is your workstation, it's pretty simple to install with pear, as described on the project page. Let's review that method and cover two others, git and Homebrew, and how to keep them updated.

All commands listed below are to be executed on the command line with the Terminal application. But if you use Drush regularly then you already knew that!

Homebrew

If you need introduced to Homebrew, or need help installing it, check out the Homebrew project homepage. The installation script is at the bottom.

Ready to install drush?

1
brew install drush

Yup, that's it! Upgrading is simple as well, as explained in the Homebrew FAQ:

1
2
brew update
brew upgrade drush

PEAR

Whether you've installed your own copy of PHP with Homebrew, are using the one included with MAMP, or the version provided by OS X, drush is easy to install using the pear command.

If you are using the built-in php with OS X, you'll need to prefix each command with sudo.

1
pear channel-discover pear.drush.org

Homebrew Note: If you are using Homebrew and get an error about not being able to create a lock file, run the following command (change php54 to php53 if applicable): touch $(brew --prefix php54)/lib/php/.lock && chmod 0644 $(brew --prefix php54)/lib/php/.lock

Continue by installing drush now that the channel is added:

1
2
pear install drush/drush
pear upgrade --force Console_Getopt

Homebrew Note: If you do not have drush available in your path after installing, relink PHP and you should then have the symlink available in /usr/local/bin: brew unlink php54 && brew link php54

Upgrading is also easy (again, if using the built-in PHP with OS X, prefix with sudo):

1
2
pear channel-update pear.drush.org
pear upgrade drush

Git

If you would prefer to keep track of the source code for Drush, or perhaps be a contributor, you will want to use Git so you can easily make and keep track of changes. If you do not have git installed on your system, install Xcode or Command Line Tools for Xcode and then proceed. These commands will clone the drush repository, checkout the latest stable tag, and create a symlink in /usr/local/bin:

1
2
3
4
5
6
[ ! -d /usr/local ] && sudo mkdir /usr/local && sudo chgrp admin /usr/local && sudo chmod g+rwx /usr/local
git clone http://git.drupal.org/project/drush.git /usr/local/drush
cd /usr/local/drush
git checkout $(git tag|grep "^[0-9]"|egrep -v "alpha|beta|rc"|tail -1)
cd /usr/local/bin 2>/dev/null || mkdir /usr/local/bin && cd /usr/local/bin
ln -s ../drush/drush || sudo ln -s ../drush/drush

To update, fetch the latest code and grab the latest stable tag:

1
2
3
cd /usr/local/drush
git fetch
git checkout $(git tag|grep "^[0-9]"|egrep -v "alpha|beta|rc"|tail -1)

If you have any questions or improvements, please leave a comment!

Never Touch Your Local /etc/hosts File in OS X Again

In each of my posts on setting up a local development environment on OS X, it's mentioned that you need to add your website's domain, even though it's local, in your /etc/hosts file. My preferred way to edit the hosts file on OS X is using Gas Mask. If you wanted to create the local virtual host projectx.dev, you would add the line 127.0.0.1 projectx.dev in /etc/hosts or with Gas Mask, and then use that same value in either ServerName in Apache or server_name in Nginx. This can be tedious for adding new sites. Luckily there's a way to set this up once and then never have to edit your hosts file again for adding new local virtual hosts.

You'll need a copy of dnsmasq, and I find this is most easily installed via Homebrew. If you haven't already, grab either Xcode or Xcode Command Line Tools and install Homebrew.

The steps below will install dnsmasq from Homebrew, configure dnsmasq to return the IP address '127.0.0.1' for all requests to the fake top-level-domain ".dev," start dnsmasq on boot (don't worry, it's an extremely light-weight process), and configure OS X to use dnsmasq for queries ending in ".dev."

1
2
3
4
5
6
7
brew install dnsmasq
mkdir -pv $(brew --prefix)/etc/
echo 'address=/.dev/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
sudo cp -v $(brew --prefix dnsmasq)/homebrew.mxcl.dnsmasq.plist /Library/LaunchDaemons
sudo launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
sudo mkdir -v /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'

That's it! You can run scutil --dns to show all of your current resolvers, and you should see that all requests for a domain ending in .dev will go to the DNS server at 127.0.0.1:

1
2
3
resolver #9
  domain   : dev
  nameserver[0] : 127.0.0.1

If you ping any domain that ends in .dev, you'll get the IP address 127.0.0.1 back as a result:

1
2
3
$ ping -c 1 thereisnowaythisisarealdomain.dev
PING thereisnowaythisisarealdomain.dev (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.057 ms

Note that if you're using Mountain Lion you may need to reboot before the /etc/resolver settings take effect globally. I was able to get pings to work right away but Chrome would not resolve properly until I restarted.

Now you can set up a new virtual host with anydomain.dev, and it'll be available as soon as you reload Apache or Nginx! You could extend this by enabling Mass Virtual Hosting for Apache or similar with Nginx, though both require consistent layout of directories. Have fun configuring less stuff on your system!

Redirect All Port 80 Requests to Port 8080

In my previous post, I walked through how to set up a local environment using Nginx running on port 8080 so as to avoid running anything as root or with sudo. Something that I've found incredibly annoying is when I forget to specify the port I get an error in my browser, or Chrome might even suggest something based on a search term. It's fairly easy though to configure Apache to route everything to another port.

Create the file /etc/apache2/other/port8080-redirect.conf as root. It's probably easiest to hop into Terminal and use nano:

1
sudo nano -w /etc/apache2/other/port8080-redirect.conf

Enter the following lines and save and exit the editor.

1
2
3
4
5
6
<VirtualHost _default_:80>
  DocumentRoot /Library/WebServer/Documents
  RewriteEngine On
  # Redirect all requests to the local Apache server to port 8080
  RewriteRule ^.*$ http://%{HTTP_HOST}:8080%{REQUEST_URI}
</VirtualHost>

Even though the DocumentRoot doesn't appear to be necessary, I wasn't able to get this working without it.

Go into System Preferences, open the Sharing preference pane, and check the box for Web Sharing. If you have trouble getting the check to "stick," you can run the following in Terminal: sudo apachectl restart

Now if you go in your browser and hit a domain you have defined in /etc/hosts, like http://drupal.local/, you'll automatically be redirected to http://drupal.local:8080/

OS X 10.7 Lion Development: Nginx, PHP, MariaDB With Homebrew

Nginx is quickly becoming a popular, low resource alternative to Apache for many websites. This doesn't come without challenges, such as using PHP as CGI due to not having mod_php available. Nginx also does not use any Apache configuration rules, nor does it use .htaccess or anything like it, so it requires additional configuration regardless of the web application being deployed. A big help in getting Nginx started with Drupal is António P. P. Almeida's drupal-with-nginx configuration, which makes it fairly simple to deploy in Linux. But what about local development on OS X? Read on to learn get all of the required components set up for your system, as well as the modifications necessary to get drupal-with-nginx set up on OS X.

Prerequisites

We'll be building all of our packages with Homebrew, which is, in my opinion, one of the best ways to easily add lot's of great open-source software on OS X. Homebrew requires that you have a compiler, so you can either install the huge Xcode package, or I would recommend Apple's Xcode Command Line Tools which is a much smaller download and officially supported by Homebrew.

Once you have either Xcode or Xcode Command Line Tools installed, install Homebrew.

Note that for all commands below that are starting with a $, the dollar sign is showing a command-line prompt in Terminal, and you should not actually type it as part of the commands. I also make heavy use of $(brew --prefix) to make these instructions persist passed current Homebrew formula versions, and hopefully also for an installation with Homebrew in a path other than /usr/local, though I have not tested it.

Also note that many times in this post you will see /n; make sure you type those or include them with your copy and paste, they are not CMS errors :)

Database: MariaDB

I've covered before why I like MariaDB, but you could easily swap this out with MySQL if you would rather. We'll start by installing MariaDB with Homebrew.

1
2
3
4
5
6
7
8
9
brew install mariadb pidof (note: OS X may ask you if you want to install 'javac')
unset TMPDIR
mysql_install_db --verbose --user=`whoami` --basedir="$(brew --prefix mariadb)" --datadir=$(brew --prefix)/var/mysql --tmpdir=/tmp
cp $(brew --prefix mariadb)/share/mysql/my-small.cnf $(brew --prefix mariadb)/my.cnf
sed -i "" 's/max_allowed_packet = 1.*M/max_allowed_packet = 2G/g' $(brew --prefix mariadb)/my.cnf
[ ! -d ~/Library/LaunchAgents ] && mkdir ~/Library/LaunchAgents
[ -f $(brew --prefix mariadb)/homebrew.mxcl.mariadb.plist ] && cp -v $(brew --prefix mariadb)/homebrew.mxcl.mariadb.plist ~/Library/LaunchAgents/
[ -f ~/Library/LaunchAgents/homebrew.mxcl.mariadb.plist ] && launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.mariadb.plist
$(brew --prefix mariadb)/bin/mysql_secure_installation
Note: you could alternatively run: `$(brew –prefix mariadb)/bin/mysqladmin -u root password ‘new-password’` instead of `mysql_secure_installation` ## PHP OS X comes with PHP installed, but it doesn’t come with PHP-FPM. While it’s likely possible to run PHP as FastCGI with the built-in OS X, I prefer to install PHP with Homebrew since we’re using Homebrew for everything else, and it keeps everything self-contained in Homebrew’s root (defaults to /usr/local). Note that the `brew tap` command requires [Homebrew 0.9 or greater](https://github.com/mxcl/homebrew/wiki/Homebrew-0.9).
1
2
3
4
5
6
7
8
brew tap josegonzalez/php
brew install php --with-mariadb --with-suhosin --with-fpm
mkdir -v $(brew --prefix php)/var/log
cp -v $(brew --prefix)/etc/php-fpm.conf.default $(brew --prefix)/etc/php-fpm.conf
sed -i '' 's|;\(daemonize[[:space:]]*=[[:space:]]*\)yes|\1no|g' $(brew --prefix)/etc/php-fpm.conf
[ ! -d ~/Library/LaunchAgents ] && mkdir ~/Library/LaunchAgents
[ -f $(brew --prefix php)/org.php-fpm.plist ] && cp -v $(brew --prefix php)/org.php-fpm.plist ~/Library/LaunchAgents/
[ -f ~/Library/LaunchAgents/org.php-fpm.plist ] && launchctl load -w ~/Library/LaunchAgents/org.php-fpm.plist

I would recommend the following settings for full compatibility with drupal-with-nginx, and to set the time zone to silence a lot of PHP warnings:

1
2
3
4
sed -i '' 's|;\(pm.status_path[[:space:]]*=[[:space:]]*/\)\(status\)|\1fpm-\2|g' $(brew --prefix)/etc/php-fpm.conf
sed -i '' 's|;\(ping.path[[:space:]]*=[[:space:]]*/ping\)|\1|g' $(brew --prefix)/etc/php-fpm.conf
sed -i '' 's|;\(ping.response[[:space:]]*=[[:space:]]*pong\)|\1|g' $(brew --prefix)/etc/php-fpm.conf
sed -i '' "s|;\(date\.timezone[[:space:]]*=\).*|\1 $(php -d 'error_reporting=' -r 'echo date("e", time());')|g" $(brew --prefix)/etc/php.ini

By default, PHP-FPM runs on a socket, which means that connections to PHP-FPM will require using TCP. You also have the option to use Unix sockets, which means slightly less overhead in PHP-FPM connections. Note that the drupal-with-nginx repository is set up for TCP by default, though if you choose to run the following command I will tell you how to use Unix sockets with Nginx.

1
sed -i '' 's|\(listen[[:space:]]*=[[:space:]]*\)127.0.0.1:9000|\1var/www.sock|g' $(brew --prefix)/etc/php-fpm.conf

Optional: PHP Extensions

The third-party Homebrew keg that we "tapped" into also provides easy formulas for PHP extensions. None of these are required to run Nginx and Drupal locally. You may also note that uploadprogress does not work with anything but mod_php, but by installing it now you could theoretically use the same PHP installation with Apache if you wanted and already have it ready to go. Feel free to omit it, or any of these below, though I would at least recommend APC for performance reasons.

1
2
3
4
5
6
7
8
9
10
11
brew install uploadprogress-php
echo -e "\n[uploadprogress]\nextension=\"$(brew --prefix uploadprogress-php)/uploadprogress.so\"" >> $(brew --prefix)/etc/php.ini
brew install apc-php
echo -e "\n[apc]\nextension=\"$(brew --prefix apc-php)/apc.so\"\napc.enabled=1 \napc.shm_segments=1 \napc.shm_size=64M \napc.ttl=7200 \napc.user_ttl=7200 \napc.num_files_hint=1024 \napc.mmap_file_mask=/tmp/apc.XXXXXX \napc.enable_cli=1" >> $(brew --prefix)/php.ini
brew install memcache-php
echo -e "\n[memcache]\nextension=\"$(brew --prefix memcache-php)/memcache.so\"" >> $(brew --prefix)/etc/php.ini
echo -e "memcache.hash_strategy=\"consistent\"" >> $(brew --prefix)/etc/php.ini
brew install xdebug-php
echo -e "\n[xdebug]\nzend_extension=\"$(brew --prefix xdebug-php)/xdebug.so\"" >> $(brew --prefix)/etc/php.ini
brew install xhprof-php
echo -e "\n[xhprof]\nextension=\"$(brew --prefix xhprof-php)/xhprof.so\"" >> $(brew --prefix)/etc/php.ini

Once you've finished configuring PHP, you can reload the settings for PHP-FPM (or, you could find the pid of the first php-fpm process and send it the SIGUSR2 signal; this is easier):

1
[ -f ~/Library/LaunchAgents/org.php-fpm.plist ] && launchctl unload -w ~/Library/LaunchAgents/org.php-fpm.plist && launchctl load -w ~/Library/LaunchAgents/org.php-fpm.plist

Nginx

After compiling MariaDB and PHP, you're probably not too excited about compiling another application. Luckily, Nginx is a faily quick build, at least compared to MariaDB and PHP. We'll include some build options not on by default since they add features references in drupal-with-nginx, and we'll also add some 3rd party extensions as well. We'll start by grabbing those extensions:

1
2
curl -s -L -o /tmp/nginx-upload-progress.tar.gz https://github.com/masterzen/nginx-upload-progress-module/tarball/v0.9.0 && mkdir /tmp/nginx-upload-progress && tar zxpf /tmp/nginx-upload-progress.tar.gz --strip-components 1 -C /tmp/nginx-upload-progress && rm /tmp/nginx-upload-progress.tar.gz
curl -s -L -o /tmp/nginx-fair.tar.gz http://github.com/gnosek/nginx-upstream-fair/tarball/master && mkdir /tmp/nginx-fair && tar zxpf /tmp/nginx-fair.tar.gz --strip-components 1 -C /tmp/nginx-fair && rm /tmp/nginx-fair.tar.gz

The next section is one giant line of sed regex that will edit the Homebrew formula for nginx to add the additional compile options that we need. Make sure it all gets entered as one line (yes, you should use copy and paste here)!

1
sed -i '-default' 's/\([[:space:]]*\['\''--\)\(with-webdav\)\('\'',[[:space:]]*"\)\(Compile with support for WebDAV module\)\("\]\)/\1\2\3\4\5,%\1with-realip\3Compile with support for RealIP module\5,%\1with-gzip_static\3Compile with support for Gzip Static module\5,%\1with-uploadprogress\3Compile with support for Upload Progress module\5,%\1with-fair\3Compile with support for Fair module\5,%\1with-mp4\3Compile with support for MP4 module\5,%\1with-flv\3Compile with support for FLV module\5,%\1with-stub_status\3Compile with support for Stub Status module\5/; s/\([[:space:]]* args << "--\)\(with-http_dav_module\)\(" if ARGV.include? '\''--with-\)\(webdav\)\('\''.*\)/\1\2\3\4\5%\1with-http_realip_module\3realip\5%\1with-http_gzip_static_module\3gzip_static\5%\1add-module=\/tmp\/nginx-upload-progress\3uploadprogress\5%\1add-module=\/tmp\/nginx-fair\3fair\5%\1with-http_mp4_module\3mp4\5%\1with-http_flv_module\3flv\5%\1with-http_stub_status_module\3stub_status\5/; y/%/\n/' $(brew --prefix)/Library/Formula/nginx.rb

Now we'll install Nginx with our new build options and extensions and start it.

1
2
3
4
5
6
brew install nginx --with-realip --with-gzip_static --with-mp4 --with-flv --with-stub_status --with-uploadprogress --with-fair
[ $? -eq 0 ] && rm -rf /tmp/nginx-upload-progress /tmp/nginx-fair
mkdir -vp $(brew --prefix nginx)/var/{microcache,log,run}
[ ! -d ~/Library/LaunchAgents ] && mkdir ~/Library/LaunchAgents
[ -f $(brew --prefix nginx)/homebrew.mxcl.nginx.plist ] && cp -v $(brew --prefix nginx)/homebrew.mxcl.nginx.plist ~/Library/LaunchAgents/
[ -f ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist ] && launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist

Nginx-With-Drupal

We're now ready to set up Nginx to work with Drupal. I've created a fork of the original repository to make some necessary changes for OS X. I also make several changes for the stable 1.0.x branch of Nginx, which we've just installed, rather than the unstable 1.1.x branch that is in the original configuration.

If you want to stick with the original project, check out the changes I made for OS X and 1.0.x and you can apply them yourself and skip the git steps below.

1
2
3
4
5
6
7
8
[ -d $(brew --prefix)/etc/nginx ] && mv -v $(brew --prefix)/etc/nginx $(brew --prefix)/etc/nginx-default
git clone https://github.com/alanthing/drupal-with-nginx.git $(brew --prefix)/etc/nginx
cd $(brew --prefix)/etc/nginx
git checkout osx-1.0.x
mkdir sites-enabled
cd sites-enabled
ln -s ../sites-available/000-default
cp -a ../sites-available/example.com.conf yournewsite.conf

That's about as much as I can automate for you with copy+paste-able commands! You'll want to do the following to yournewsite.conf, which you can rename to be anything, to configure Nginx for your website:

  • Change server_name, access_log, and error_log to use your local domain name for your virtual host
  • Change root to the path of your Drupal installation. On my system, this may be /Users/alan/Sites/drupal-7.14. Note that you cannot use ~ in place of /Users/name
  • Unless you have a valid SSL certificate, you'll probably want to completely delete the second half of the file. So find the line containing } HTTP server and remove all following lines
  • If you're using Boost with Drupal 7, or Drupal 6 with/without Boost, note that you'll want to comment out the include sites-available/drupal.conf line and uncomment the other relevant one for your site
  • If you want to get additional status messages from PHP-FPM, uncomment include php_fpm_status_vhost.conf in this file, and also include php_fpm_status_allowed_hosts.conf in $(brew --prefix)/etc/nginx/nginx.conf
  • If you configured PHP-FPM earlier to use Unix sockets instead of TCP, open nginx.conf and comment out include upstream_phpcgi_tcp.conf and uncomment include upstream_phpcgi_unix.conf
  • Depending on the location of your files directory, you may need to edit the sites-available/drupal.conf (or other drupal*.conf) file and change the relevant sites/default/files paths appropriately

Once you're finished editing your virtual host conf file (and possibly nginx.conf), you can reload nginx easily:

1
$(brew --prefix)/sbin/nginx -s reload

Bonus: Drush

You'll need Drush to install a new Drupal site, as install.php is blocked for security reasons by default. Also, you'll find cron.php is inaccessible as well. There's a weird little hack required to get Drush installed with Homebrew without requiring sudo, so below is an example of how to both get around the sudo requirement and set up a blank Drupal 7 website (note that using the root MySQL user is bad form, but this is meant more as a quick demo of using drush with this setup than a recommended setup).

1
2
3
4
5
6
7
8
9
10
touch $(brew --prefix php)/lib/php/.lock
chmod 0644 $(brew --prefix php)/lib/php/.lock
$(brew --prefix)/bin/pear channel-discover pear.drush.org
$(brew --prefix)/bin/pear install drush/drush
brew unlink php && brew link php
cd ~/Sites 2>/dev/null || mkdir ~/Sites && cd ~/Sites
drush dl
mysql -uroot -p'yourpassword' -e'create database drupal;'
cd drupal-7.14
drush si --db-url=mysql://root:yourpassword@localhost/drupal

Node.js and NPM on CentOS

Update: This no longer appears to be necessary as of nodejs 0.8.0. It may have been fixed earlier but I noticed neither of these changes are necessary anymore. Something new though, I had problems with node-gyp, and the solution was to install python26 with yum and then re-run the npm command with PYTHON="/usr/bin/python26" npm install -deps or similar.

The preferred way to install node and NPM seems to be installing from source, but I'm a perennial fan of using packages to keep things tidy, especially if I need to uninstall something. I started by going to the Node.js download page, and through to Installing with a package manager. I installed the yum release RPM for the tchol.org repository as directed and installed nodejs and npm with yum. From there, I ran into two problems but thankfully they were fairly easy to resolve.

The first thing I wanted to do was install forever globally so I could use it to keep applications running persistently. But running npm install forever -g kept stalling. The npm RPM installed from the tchol.org repository creates the symlink /usr/lib/node_modules to /usr/lib/nodejs. That's fine, but /usr/lib/nodejs is owned by root:root. Running a npm install command with sudo attempted to set the ownership of the NPM modules as nobody:user, and NPM wasn't exiting due to permissions for some reason.

I have ACLs enabled on my file system, so I fixed this by allowing nobody write access to /usr/lib/nodejs:

sudo setfacl -m u:nobody:rwx /usr/lib/nodejs

If you don't have ACLs enabled on your filesystem, you could allow nobody to be the folder owner:

sudo chown nobody:nobody /usr/lib/nodejs

The second problem I ran into was, after installing forever, it wouldn't run. The nodejs package installed the binary as /usr/bin/nodejs but /usr/bin/forever begins with #!/usr/bin/env node, which will not return a valid interpreter. I wanted to install a symlink into /usr/local/bin, but some users don't always have that in their path, so I created a link via:

sudo ln -s /usr/bin/nodejs /usr/bin/node

Now I'm able to install anything I need to with NPM and run my Node.js applications with Forever. I'm still very new to Node, so if you think I should be doing things a better way, let me know in the comments!