WordPress OSX nginx MySQL PHP (WONMP)

This is an overview of my local nginx server setup. If you just want to get up and running quickly, using the built in web server on OS X Lion or even MacPorts or HomeBrew is probably a better idea for you. I’ve installed from source so that I can mess around with all the settings and because I wanted to learn how to optimize nginx—practice for my VPS. Here’s my setup:

Macbook Air 13-inch
Processor  1.8 GHz Intel Core i7
Memory  4 GB 1333 MHz DDR3

  • WordPress Trunk (3.3 r19595)
  • OS X Lion 10.7.2 (11C74)
  • nginx 1.0.10
  • MySQL 5.5.18
  • PHP 5.3.8

DO NOT use this server setup in a production setting. This is for local testing only.

1. Install XCode from the App Store. Open Xcode and go to Preferences > Downloads > Components > Command Line Tools.

2. Setup local permissions and compiler flags. Replace YOUR_USERNAME with your username.

sudo mkdir -p /usr/local
sudo chown YOUR_USERNAME:staff /usr/local
mkdir -p /usr/local/bin
mkdir -p /usr/local/src
mkdir -p /usr/local/mysql

I used the following compiler flags to force 64bit. Use uname -m to find your arch value.

CFLAGS="-arch x86_64 -g2 -Os -pipe -no-cpp-precomp"
EXTRACFLAGS="-lresolv"
CCFLAGS="-arch x86_64 -g2 -Os -pipe"
CXXFLAGS="-arch x86_64 -g2 -Os -pipe"
LDFLAGS="-arch x86_64"

3. Install MySQL.

cd /usr/local/src
curl -C - -O http://www.cmake.org/files/v2.8/cmake-2.8.6.tar.gz
tar zxvf cmake-2.8.6.tar.gz
cd cmake-2.8.6
./configure
make
make install
cd /usr/local/src
curl -C - -O http://mirror.services.wisc.edu/mysql/Downloads/MySQL-5.5/mysql-5.5.18.tar.gz
tar xzvf mysql-5.5.18.tar.gz
cd mysql-5.5.18
cmake .
make
make install
cd /usr/local/mysql
./scripts/mysql_install_db --prefix=/usr/local/mysql

Copy the small config file with cp support-files/my-small.cnf /usr/local/etc/my.cnf. For a little basic security, limit access to MySQL by adding bind-address = 127.0.0.1 to the [mysqld] section in your my.cnf file.

Run mysqld_safe & to start mysql in the background.

To setup phpMyAdmin, you can pull it using git.

cd /usr/local/nginx/html/pma
git clone git://phpmyadmin.git.sourceforge.net/gitroot/phpmyadmin/phpmyadmin .

To keep phpMyAdmin always updated to the latest version, run crontab -e and add the following line:

0 0 * * * git pull /usr/local/nginx/html/pma &1

4. Install PHP.

For extra gd options, install the libraries libjpeg, libpng, and FreeType 2. Note: I could only get FreeType 2 to compile by setting ARCH=”i386″ before configure.

cd /usr/local/src
curl -C - -O http://www.ijg.org/files/jpegsrc.v8c.tar.gz
tar zxvf jpegsrc.v8c.tar.gz
cd jpeg-8c
./configure --enable-shared
make
make install
cd /usr/local/src
curl -C - -O ftp://ftp.simplesystems.org/pub/libpng/png/src/libpng-1.5.6.tar.gz
tar zxvf libpng-1.5.6.tar.gz
cd libpng-1.5.6
./configure
make
make install
cd /usr/local/src
# http://download.savannah.gnu.org/releases/freetype/freetype-2.4.8.tar.gz
mv ~/Downloads/freetype-2.4.8.tar.gz ./
tar zxvf freetype-2.4.8.tar.gz
cd freetype-2.4.8
ARCH="i386"; ./configure
make
make install
unset ARCH
cd /usr/local/src
curl -C - -O http://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz
tar zxvf ncurses-5.9.tar.gz
cd ncurses-5.9
./configure
make
make install
cd /usr/local/src
cd /usr/local/src
# http://sourceforge.net/projects/expat/files/expat/2.0.1/expat-2.0.1.tar.gz/download
cp ~/Downloads/expat-2.0.1.tar.gz ./
tar zxvf expat-2.0.1.tar.gz
cd expat-2.0.1
./configure
make
make install
cd /usr/local/src
curl -C - -O http://ftp.gnu.org/pub/gnu/gettext/gettext-0.18.1.1.tar.gz
tar zxvf gettext-0.18.1.1.tar.gz
cd gettext-0.18.1.1
./configure
make
make install

Ran into an error while compiling gettext.

Error: “stpncpy.c:3555error: expected declaration specifiers or ‘…’ before numeric constant”
Fix: http://techshow.me/doc/276
Replace: __stnpcpy (char *dest, const char *src, size_t n)
With: __stpcpy (char *dest, const char *src, size_t n)

cd /usr/local/src
curl -C - -O ftp://ftp.gnu.org/gnu/gmp/gmp-5.0.2.tar.gz
tar zxvf gmp-5.0.2.tar.gz
cd gmp-5.0.2
./configure
make
make install
cd /usr/local/src
curl -C - -O http://www.php.net/distributions/php-5.3.8.tar.gz
tar zxvf php-5.3.8.tar.gz
cd php-5.3.8
./configure --enable-cgi --enable-fpm --with-bz2 --with-zlib --enable-mbstring --with-curl --disable-rpath --with-zlib --enable-sockets --enable-sysvsem --enable-sysvshm --enable-mbregex --enable-zip --with-pcre-regex --with-gd --disable-magic-quotes --with-jpeg-dir=/usr/local --with-mysql=/usr/local/mysql --with-gettext --with-gmp --enable-exif --disable-ipv6 --enable-inline-optimization --disable-debug
make
make install

Ran into an error while compiling PHP.

Error: “Undefined symbols for architecture x86_64: “_res_9_init”, referenced from:_zif_dns_get_mx in dns.o ld: symbol(s) not found for architecture x86_64″
Fix: https://bugs.php.net/bug.php?id=55108

sudo install_name_tool -id /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/local/mysql/lib/libmysqlclient.dylib

Configure php-fpm and test the install to make sure it returns the right version. Update YOUR_USERNAME and YOUR_GROUPNAME to your username and group.

cp /usr/local/src/php-5.3.8/sapi/fpm/php-fpm /usr/local/sbin/
cp /usr/local/src/php-5.3.8/sapi/fpm/php-fpm.conf /usr/local/etc/
cp /usr/local/src/php-5.3.8/php.ini-development /usr/local/lib/php.ini
perl -pi -e 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /usr/local/lib/php.ini
perl -pi -e 's/;pm.start_servers = 20/pm.start_servers = 10/' /usr/local/etc/php-fpm.conf
perl -pi -e 's/;pm.min_spare_servers = 5/pm.min_spare_servers = 2/' /usr/local/etc/php-fpm.conf
perl -pi -e 's/;pm.max_spare_servers = 35/pm.max_spare_servers = 25/' /usr/local/etc/php-fpm.conf
perl -pi -e 's/^user = nobody/user = YOUR_USERNAME/' /usr/local/etc/php-fpm.conf
perl -pi -e 's/^group = nobody/group = YOUR_GROUPNAME/' /usr/local/etc/php-fpm.conf

Run php-fpm -v to check the version. Run sudo php-fpm to start it up. To stop php-fpm, you need to find the process and kill it. Run ps aux | php-fpm, find the lowest pid, and run the kill command for that number.

5. Install nginx.

Requirements: gzip module requires zlib library, rewrite module requires pcre library, check phpinfo() or php -i to see if you already have them.

cd /usr/local/src
curl -C - -O ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.20.tar.gz
tar zxvf pcre-8.20.tar.gz
cd pcre-8.20
./configure
make
make install

After you checkout nginx_auth_mysql, edit nginx_auth_mysql/config and add the following lines. Make sure to update the paths to the MySQL libraries if your paths are different.

CORE_LIBS="$CORE_LIBS -lcrypto -lmysqlclient -L/usr/local/mysql/lib/"
CORE_INCS="$CORE_INCS /usr/local/mysql/include/"

cd /usr/local/src
svn co http://code.svn.wordpress.org/nginx_auth_mysql/ ./nginx_auth_mysql/
curl -C - -O http://nginx.org/download/nginx-1.0.10.tar.gz
tar zxvf nginx-1.0.10.tar.gz
cd nginx-1.0.10
./configure --with-http_stub_status_module --sbin-path=/usr/local/sbin/nginx --add-module=../nginx_auth_mysql/
make
make install

Create a /usr/local/nginx/conf/nginx.conf file. Replace YOUR_USERNAME with your username.

user YOUR_USERNAME staff;
worker_processes 2;
daemon off;

events {
        worker_connections 128; # output of ulimit -n or smaller
}

http {
        index index.html index.htm index.php;
        include mime.types;
        default_type application/octet-stream;
        keepalive_timeout 3 2; 
        sendfile on; 
        tcp_nopush on; 
        tcp_nodelay off;
        autoindex on; 

        log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status  $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';
        access_log /usr/local/nginx/logs/access.log main;
        error_log /usr/local/nginx/logs/error.log debug;
        include /usr/local/nginx/sites/*;
}

Make sure /usr/local/nginx/sites exists, and add a localhost file.

upstream php {
        server 127.0.0.1:9000;
}

server {
        listen 80; 
        server_name localhost;
        root /usr/local/nginx/html;

        location / { 
                try_files $uri $uri/ /index.php =404;
                error_page 404 = /index.php?s=$uri;
        }   

        location ~ /\.ht {
                deny all; 
        }   

        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }   

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }   

        location ~* \.php$ {
                try_files $uri =404;
                fastcgi_pass php;
                include /usr/local/nginx/conf/fastcgi.conf;
        }   
}

Switch the port for Mac OS X Lion’s default web server to something else so you can use port 80 for your own purposes. Open /etc/apache2/httpd.conf and replace Listen 80 with Listen 8040 (or whatever port you’d like). Then restart apache with sudo apachectl restart.

Start nginx as root with sudo nginx. To stop nginx, use sudo nginx -s stop. To restart nginx, use sudo nginx -s reload.

6. Install WordPress.

Setup a MySQL user. Replace username and password withs values of your choice.

mysql -uroot -e "CREATE user 'username'@'localhost' IDENTIFIED BY 'password';"

Setup MySQL databases, download WordPress via svn, and open the URL to finish the install process.

MYDB="wp33"
mysql -uroot -e "DROP DATABASE IF EXISTS $MYDB; CREATE DATABASE $MYDB; GRANT ALL PRIVILEGES ON $MYDB.* TO 'username'@'localhost' IDENTIFIED BY 'password'; FLUSH PRIVILEGES;"
mkdir -p /usr/local/nginx/html/3.3/
cd /usr/local/nginx/html/3.3/
svn co http://core.svn.wordpress.org/tags/3.3/ .
open http://localhost/3.3/
MYDB="trunk"
mysql -uroot -e "DROP DATABASE IF EXISTS $MYDB; CREATE DATABASE $MYDB; GRANT ALL PRIVILEGES ON $MYDB.* TO 'username'@'localhost' IDENTIFIED BY 'password'; FLUSH PRIVILEGES;"
mkdir -p /usr/local/nginx/html/trunk/
cd /usr/local/nginx/html/trunk/
svn co http://core.svn.wordpress.org/trunk/ .
open http://localhost/trunk/

To update WordPress to the latest version of trunk hourly, run crontab -e and add the following line:

0 * * * * svn up /usr/local/nginx/html/trunk >/dev/null 2>&1

Voilà!

P.S. I don’t usually like made-up acronyms, but WONMP just seemed cute.