Are you using Nginx, PHP, PHP-FPM and
MySQL on your Linux servers? Are you totally in love with this
setup? I do! But, as a Web developer, I also need a local Web
server on my Windows machine for testing. So far, I've always been
using the standard "WAMP" setup: Windows, Apache, MySQL, PHP. Why
couldn't I run Nginx and PHP-FPM on my Windows machine as well?
Today, I will teach you how to achieve it so you can really say
goodbye to Apache!
This post covers the installation and configuration of the
following products:
- MySQL Win32 (server and client)
- Cygwin
- Nginx under Cygwin
- MySQL (client only) under Cygwin
- PHP and PHP-FPM under Cygwin
IMPORTANT NOTES
- This post was tested on Windows XP Pro SP3. I have no idea if it
will work with Windows Vista.
- This post assumes that you are logged on to your Windows machine
as the "Administrator" user. Always replace "Administrator" by your
actual Windows username.
- This post assumes that you install MySQL Win32 (server and
client) in c:\mysql. You can install it where you want, simply
replace c:\mysql by your actual installation path.
- This post assumes that you install Cygwin in c:\cygwin. You can
install it where you want, simply replace c:\cygwin by your actual
installation path.
- To extract ".tar.gz" files under Windows, you can use WinRAR.
- If you're going to install this setup, PLEASE read this post
COMPLETELY before installing!
Table of contents
1 - Getting the needed files
2 - Installing MySQL Win32 (server and client)
3 - Installing Cygwin
4 - Installing Nginx under Cygwin
5 - Installing MySQL (client only) under Cygwin
6 - Installing PHP and PHP-FPM under Cygwin
7 - Creating Windows services
8 - Using your new installation
Conclusion
Resources
Annex 1 - Simple benchmark
1 - Getting the needed files
Download these files to your Windows machine:
http://www.cygwin.com/setup.exe
http://dev.mysql.com/get/Downloads/M.../mysql_mirror/
http://sysoev.ru/nginx/nginx-0.6.32.tar.gz
http://dev.mysql.com/get/Downloads/M.../mysql_mirror/
http://php-fpm.anight.org/downloads/...-0.5.9.diff.gz
http://us3.php.net/get/php-5.2.6.tar...om/this/mirror
http://www.sra.co.jp/people/m-kasahr...o-1.6.3.tar.gz
If the PHP-FPM link isn't working, go here:
http://php-fpm.anight.org/download.html
And download the latest version of PHP-FPM for PHP 5.2.6.
2 - Installing MySQL Win32 (server and
client)
First of all, unpack mysql-noinstall-5.0.67-win32.zip in c:\ and
rename the mysql-5.0.67-win32 folder to mysql.
Then, create the c:\my.cnf file with the following lines:
c:\my.cnf:
[mysqld]
basedir=c:/mysql
datadir=c:/mysql/data
Now, open a Command Prompt window (Start, Run, "cmd" - or Win+R,
"cmd") and type:
REM " Change to the MySQL bin
directory
cd c:\mysql\bin
REM " Install the MySQL service
mysqld-nt --install
REM " Start MySQL
net start mysql
MySQL is now running. It's a good idea to change the root password.
In your Command Prompt Windows, type:
REM " Change the MySQL root password
mysqladmin -u root password pass
By default, the MySQL service will start with Windows. You can
change this behavior in Control Panel, Administrative Tools,
Services.
3 - Installing Cygwin
Run setup.exe and hit "Next" 6 times (you want "Install from
Internet", choose the mirror of your choice). You will arrive at a
huge list of packages to choose from. Tip: you can maximize
that window.
To select a package, click in the "New" column (it cycles through
Skip, Keep, Uninstall, etc.). When a version number is displayed,
it will be installed.
Select these additional packages (dependencies for these packages
will be automatically selected):
Admin/cygrunsrv
Devel/autoconf
Devel/automake
Devel/bison
Devel/curl-devel
Devel/flex
Devel/gcc
Devel/libiconv
Devel/libmcrypt-devel
Devel/libtool
Devel/libxml2
Devel/libxml2-devel
Devel/make
Devel/patchutils
Devel/pcre
Devel/pcre-devel
Editors/vim
Libs/jpeg
Libs/libmcrypt
Then hit "Next" to install. Tip: you can run setup.exe
anytime to add or remove packages.
NOTE: these packages are those I needed for my own Nginx,
MySQL and PHP ./configure's options. If your ./configure's options
differ from mine, you may need to select additional packages or
your ./configure's will fail for missing dependencies.
--
Now that Cygwin is installed, run c:\cygwin\Cygwin.bat to get a
shell Window. Your home directory (c:\cygwin\home\Administrator) will be created and prepared.
Copy the following files in c:\cygwin\home\Administrator:
nginx-0.6.32.tar.gz
mysql-5.0.67.tar.gz
php-5.2.6.tar.gz
php-5.2.6-fpm-0.5.8.diff.gz
getaddrinfo-1.6.3.tar.gz
4 - Installing Nginx under Cygwin
Open Cygwin and extract nginx-0.6.32.tar.gz:
cd
tar xvfz nginx-0.6.32.tar.gz
cd nginx-0.6.32
Now, configure Nginx:
./configure --with-http_ssl_module
--without-mail_pop3_module --without-mail_imap_module
--without-mail_smtp_module
And finally, compile and install Nginx:
make
make install
--
Now we will prepare our "www" directory:
mkdir /www
As well as our Nginx configuration:
/usr/local/nginx/conf/nginx.conf:
worker_processes 5;
events {
worker_connections 64; # VERY important. Do not leave 1024 there.
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /www;
index index.php index.html;
}
location ~ \.php$ {
root /www;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}
You may adjust this configuration file to suit your needs.
5 - Installing MySQL (client only) under
Cygwin
We install the MySQL client only to be able to compile PHP with
MySQL support.
Open Cygwin and extract mysql-5.0.67.tar.gz:
cd
tar xvfz mysql-5.0.67.tar.gz
cd mysql-5.0.67
Now, configure MySQL:
./configure
--without-server
And finally, compile and install MySQL:
make
make install
6 - Installing PHP and PHP-FPM under
Cygwin
Open Cygwin and extract php-5.2.6.tar.gz:
cd
tar xvfz php-5.2.6.tar.gz
Then, patch PHP with PHP-FPM:
gzip -cd php-5.2.6-fpm-0.5.7.diff.gz |
patch -d php-5.2.6 -p1
Under Linux, you would be ready to configure, make and install PHP,
but under Cygwin, there's a little fix to apply...
Applying the getaddrinfo fix
When I first tried to compile PHP with PHP-FPM under Cygwin, I ran
into troubles with getaddrinfo. I tried to fix it myself but I only
found 50% of the solution. So I contacted Andrei Nigmatulin, the
creator of PHP-FPM, and he helped me with this problem. Here's what
you need to do:
cd
tar xvfz getaddrinfo-1.6.3.tar.gz
cp getaddrinfo/getaddrinfo.c getaddrinfo/getaddrinfo.h
php-5.2.6/sapi/cgi/fpm
Then, edit php-5.2.6/sapi/cgi/fpm/fpm_sockets.c and add the
following line after the other includes:
php-5.2.6/sapi/cgi/fpm/fpm_sockets.c:
#include
"getaddrinfo.h"
Then, you need to add "getaddrinfo.c" to the FPM_SOURCES variable
in php-5.2.6/sapi/cgi/fpm/config.m4:
php-5.2.6/sapi/cgi/fpm/config.m4:
FPM_SOURCES="fpm.c \
fpm_conf.c \
fpm_signals.c \
fpm_children.c \
fpm_worker_pool.c \
fpm_unix.c \
fpm_cleanup.c \
fpm_sockets.c \
fpm_stdio.c \
fpm_env.c \
fpm_events.c \
fpm_php.c \
fpm_process_ctl.c \
fpm_shm.c \
getaddrinfo.c \
xml_config.c \
zlog.c"
Then, you have to rebuild the PHP Makefile:
cd php-5.2.6
./buildconf --force
--
We are now ready to configure PHP:
./configure --enable-fastcgi --enable-fpm
--with-mcrypt --with-zlib --enable-mbstring --with-openssl
--with-mysql --with-gd --with-jpeg-dir --enable-gd-native-ttf
--without-sqlite --disable-pdo --disable-reflection --with-curl
--with-iconv
Then, make and install PHP:
make all install
And finally, install the default php.ini file:
cp php.ini-recommended
/usr/local/lib/php.ini
You can edit /usr/local/lib/php.ini to suit your needs.
You can also edit /usr/local/etc/php-fpm.conf if you need.
7 - Creating Windows services
The Cygwin application "cygrunsrv" allows you to create Windows
services. We definitely want to do this for Nginx and PHP.
First of all, in Cygwin, let's create the services scripts:
/home/Administrator/php.sh:
#!/bin/sh
function handleQuit
{
echo "STOPPING"
su Administrator -c
"/usr/local/sbin/php-fpm stop"
exit
}
echo "STARTING"
su Administrator -c
"/usr/local/sbin/php-fpm start"
trap "handleQuit" SIGQUIT
echo "WAITING"
while true
do
sleep 1
done
/home/Administrator/nginx.sh:
#!/bin/sh
function handleQuit
{
echo "STOPPING"
su Administrator -c "kill -QUIT $(cat
/usr/local/nginx/logs/nginx.pid)"
exit
}
echo "STARTING"
su Administrator -c
"/usr/local/nginx/sbin/nginx"
trap "handleQuit" SIGQUIT
echo "WAITING"
while true
do
sleep 1
done
Then, under Windows, use the following batch file to create the
services:
cywgin_install_services.bat:
@echo off
echo.
cd c:\cygwin\bin
echo Stopping services...
net stop php
net stop nginx
echo Removing services...
cygrunsrv -R php
cygrunsrv -R nginx
echo Installing services...
cygrunsrv --install php --path /home/Administrator/php.sh --desc "PHP-FPM" -t auto
--termsig QUIT --shutdown
cygrunsrv --install nginx --path /home/Administrator/nginx.sh --desc "Nginx" -t auto
--termsig QUIT --shutdown
echo These services are now installed:
echo.
cygrunsrv -L
echo.
pause
Voila! Our Windows services are created! But... how does it
work?
Let's examine the creation of a service, with some
comments...
cygrunsrv
# Name of the service
--install php
# Path of the file to execute
--path /home/Administrator/php.sh
# Description of the service
--desc "PHP-FPM"
# Startup type (auto or manual)
-t auto
# Signal to send to the service when stopping it
--termsig QUIT
# Stop the service when shutdowning Windows
--shutdown
When /home/Administrator/php.sh is invoked
by Windows to start the service, we start PHP-FPM as Administrator (because we don't want to run as
SYSTEM). Then, we order the script to trap QUIT and to execute the
handleQuit() function when it gets one. Then, we wait for something
to happen with an infinite while/sleep loop. When a QUIT signal is
caught (Windows wants to stop the service), the handleQuit()
function is executed: PHP-FPM is stopped, and the script
exits.
8 - Using your new installation
To start/stop MySQL on your Windows machine, use the following
commands:
REM " Start MySQL
net start mysql
REM " Stop MySQL
net stop mysql
To start/stop PHP on your Windows machine, use the following
commands:
REM " Start PHP
net start php
REM " Stop PHP
net stop php
To start/stop Nginx on your Windows machine, use the following
commands:
REM " Start Nginx
net start nginx
REM " Stop Nginx
net stop nginx
So if you start MySQL, Nginx and PHP, and that you create this
file...
/www/index.php:
"Hello world!";
...then you should be able to point http://locahost with your Web brower and see
"Hello world!".
--
VERY important note about PHP and mysql_connect
Don't do this:
mysql_connect("localhost","root","pass");
It won't work because then PHP wants to use the mysql.sock file
(/tmp/mysql.sock or /var/run/mysql.sock) and this file doesn't work
under Cygwin.
So instead, do this:
mysql_connect("127.0.0.1","root","pass");
By using the IP address, PHP will connect to MySQL using TCP
networking. So far, it's the only limitation I've found with this
setup.
Conclusion
That's it! You did it! You've just replaced your old WAMP setup by
a faster Nginx+PHP+PHP-FPM (WNMP?) setup! I ran a pretty simple
benchmark (see Annex 1 below) with both setups and, on my machine,
my new WNMP setup is around 33% faster than my old WAMP
setup!
Thanks for reading... all comments are welcome!
Resources
Nginx Web site
http://nginx.net/
Nginx English Wiki
http://wiki.codemongers.com/Main
http://wiki.codemongers.com/NginxCommandLine
And thanks to Andrei Nigmatulin, the creator of PHP-FPM, for his
help.
Annex 1 - Simple benchmark
I ran a very simple benchmark on my 2 setups:
- connect to MySQL
- flush test table
- start timer
- insert 10,000 records in test table
- select the 10,000 records from test table
- display each record
- close connection to MySQL
- display timer
The average time for Nginx was 2 seconds while Apache had an
average of 3.1 seconds.
If you use this script, don't forget to adjust the root password
for mysql_connect! And don't run the test on both setups at the
same time because they share the same test table.
SQL:
CREATE DATABASE test;
USE test;
CREATE TABLE test (
pk bigint auto_increment,
a varchar(50),
b varchar(50),
c varchar(50),
d varchar(50),
primary key(pk)
);
index.php:
// Debug
function function
debug($d)
{
print "
".print_r($d,true)."";
} // Include the timer class file
require_once("timer.php"); // Connect to the database
mysql_connect("127.0.0.1","root","pass"); // Use the test database mysql_select_db("test"); // Remove everything from the test
table mysql_query("delete from
test;"); // OK we're ready to begin the test, start the
timer $timer
= new Timer(); // Insert 10,000 records in the test
table for (
$i = 1;
$i <= 10000;
$i++ )
{ mysql_query("insert into
test (a,b,c,d) values ('a','b','c','d');");
} // Fetch these 10,000 records
$res = mysql_query("select *
from test;"); // Display these 10,000 records
while ( $row =
mysql_fetch_object($res) )
{ debug($row);
} // We're done, close the MySQL
connection mysql_close(); // Display the time print "
";
print $timer->fetch();
timer.php:
class
Timer {
var $s;
function Timer() { $this->s =
$this->getmicrotime();
}
function fetch($decimalPlaces = 3) {
return round(($this->getmicrotime()
- $this->s),
$decimalPlaces);
}
function getmicrotime() {
list($usec,
$sec) = explode("
", microtime());
return
((float)$usec
+ (float)$sec);
}
}