Note: The PHP EasyWindows installer that I wrote will install PHP+FastCGI and configure IIS for you.
As a Windows user and a former trainer on ASP and IIS, I have always wanted to build high performance PHP applications running IIS. Although I use Apache and Linux all the time, some of our customers are more comfortable with Windows and IIS.
The recommended way to run stable PHP applications in IIS is CGI. But since a new process is created for each request and thrown away when the request is done, CGI efficiency is poor. Furthermore the PHP ISAPI dll was never stable enough for my needs, because ISAPI requires thread-safety, but many PHP extensions are not thread-safe.
Fortunately there is an alternative open technology called FastCGI. FastCGI processes are persistent as they are reused to handle multiple requests. This solves the CGI performance problem of creating new processes for each request. In benchmarks, you can expect a x4 to x8 improvement in performance compared to plain-old CGI.
Shane Caraveo of ActiveState is the author of the FastCGI wrapper for IIS. He first released it in January 2002, and from my testing it appears to be stable enough for production use. 26 Jan 2004 Update: Zend, the company behind PHP's core engine, is also recommending FastCGI for Windows. See Zend WinEnabler.
Here's how it works:
- IIS loads the FastCGI Process Manager (isapi_fcgi.dll) on startup.
- The FastCGI Process Manager initializes itself, creating multiple FastCGI processes (php.exe's in the Task Manager) and waits for a new connection from the Web server.
- When a client request comes in, the FastCGI Process Manager selects and opens a connection to a FastCGI process. The server sends the CGI environment variable information and standard input over to the FastCGI process.
- Then the FastCGI process completes its processing and sends the standard output and error information back to the server over the same connection.
- When the FastCGI process closes the connection, the request is complete. The FastCGI process then waits for another connection from the FastCGI Process Manager running in IIS. In normal CGI, php.exe would be terminated at this point.
In the above example, you can imagine how much slower CGI is. For every web request PHP would need to re-parse php.ini, reload all the dll extensions and reinitialize all data strucures. With FastCGI, all this is done once per process startup. As an extra bonus, persistent database connections also work.
BenchmarksThe stability of PHP in FastCGI mode is exceptional. I have many long scripts which have problems running in ISAPI, but work beautifully in FastCGI and CGI mode. I also tested with apache bench. Here are my results for a test that includes a query to Oracle, using persistent connections, simulating 5 concurrent web client connections, using PHP 4.3.1 and IIS 5 on a W2K server (P3 800 Mhz, 256 Mb RAM) using the following command:
ab -c5 -n100 -k http://server/testoracle.php
Requests per second: 3.33 Time per request : 1.50 secs
The same query with CGI:
Requests per second: 0.44 Time per request : 11.49 secs
In other words, performance improved by 766% when using FastCGI. This is consistent with Shane's findings. A stress test with 10,000 requests worked fine with no memory leaks.
Note: the time per request is longer than the requests/second would indicate because we tested concurrently with 5 threads querying at the same time. So with FastCGI, although one request completes in 0.211 seconds, because there are 5 threads, the mean time per request across all threads is 0.042 seconds.
Quick Installation on IIS
I found Shane's instructions a bit confusing if you are not a guru, so i have written this guide to help others get FastCGI working on IIS:
- Make sure you have PHP 4.3.x or later installed. Earlier versions of PHP require extra work to get FastCGI working.
- This example assumes your cgi application is located in c:\php\php.exe.
- Download http://www.caraveo.com/fastcgi/fastcgi-0.6.zip and unpack isapi_fcgi.dll to c:\php\isapi_fcgi.dll.
- Create the following registry key with regedit.exe:
- Then add to this key the following values:
AppPath = c:\php\php.exe BindPath = php-fcgiAlternatively, you can download and run the sample registry file fastcgi.reg. You might want to change the AppPath of the sample.
- Add the application mappings extensions you want to be sent to the fastcgi dll using the IIS configuration screens. To do so:
- From the Internet Service Manager (MMC), select the Web site or the starting point directory of an application.
- Open the directory's property sheets (by right clicking and selecting properties), and then click the Home Directory, Virtual Directory, or Directory tab.
- Click the Configuration button, and then click the App Mappings tab.
- Click Add, set the file extension to .php, and in the Executable box, type: c:\php\isapi_fcgi.dll, and check the Check that file exists checkbox and save the mapping.
- Do the same for similar extensions such as .php3 or .phtml.
- Then save all changes and restart the web-server.
- To test, request for multiple web pages at the same time, and observe the processes in the Task Manager. After the web requests complete, the php.exe processes will continue running.
Unlike Apache, there appears to be no way to limit the number of requests a FastCGI process (php.exe) can handle, before restarting it. However it appears that process restarts are automatically occurring, as the process id's are changing slowly over time.
To detect whether FastCGI is running, check $_SERVER['FCGI_SERVER_VERSION']. It should hold something like "2.2.2 0.5.2 beta".
Addendum: Shane's Documentation
Most keys have defaults, and do not need to be set in the registry. Requird keys will be marked so.
create the registry keys: HKEY_LOCAL_MACHINE\Software\FASTCGI MaxPostData REG_DWORD 0 (Byte limit for pre-reading the post data) CustomVars REG_BINARY 0 (newline deliminated, null terminated list of custom environment names, can be used to specify additional environment names that the web interface should look for in addition to the defaults) ThreadPoolSize REG_DWORD 10 (IIS ONLY, 10 is default if not specified) BypassAuth REG_DWORD 0 IIS ONLY, if 1 and IIS is configured to use isapi_fcgi.dll as a filter, and IIS is configured to use BASIC authentication, this will force all authentication requests to use the IIS anonymous user. This in effect allows scripts to implement their own authentication mechanisms. Impersonate REG_DWORD 1 IIS ONLY, if 1, IIS security tokens will be used to impersonate the IIS authenticated (or anonymous) users. This is on by default. setting to zero dissables this feature. Don't dissable it unless you are not worried about security. StartServers REG_DWORD 5 Global value, used if no server specific setting IncrementServers REG_DWORD 2 Global value, used if no server specific setting MaxServers REG_DWORD 25 Global value, used if no server specific setting Timeout REG_DWORD 600 Global value, used if no server specific setting. How long (seconds) extra servers (number above StartServers) are kept alive before being terminated
create a key for each extension: HKEY_LOCAL_MACHINESoftware\FASTCGI\.php REQUIRED
inside that key, add the following values:
BindPath REG_SZ "php-fcgi" REQUIRED AppPath REG_SZ c:\php\phpfcgi.exe Args REG_SZ "" StartServers REG_DWORD 5 IncrementServers REG_DWORD 2 MaxServers REG_DWORD 25 Environment REG_BINARY (newline deliminated, null terminated, for adding additional environment variables ie. PHP_FCGI_MAX_REQUESTS=25) Filter REG_DWORD 0 If set to 1, this fastcgi server will be treated as a FastCGI FILTER. A Filter server receives the file to be filtered from the web server. for more information, see documentation at fastcgi.com Timeout REG_DWORD 600 How long extra servers (number above StartServers) are kept alive before being terminated
If the fast cgi application is started by an external process manager you only need to use the BindPath.
You can set BindPath to a ip address/port if you need to use an interpreter on a different machine.
Multiple extensions can have the same BindPath, for instance, mapping .pl and .cgi to perl.exe.
If IncrementServers is not defined, it defaults to 1/2 StartServers. If MaxServers is not defined, it defaults to 25.
How the FastCGI Process Manager works
It's fairly simple and needs to be further fleshed out. At startup, it starts "StartServers" number of executables for each binding. When running, if none are available, it starts "IncrementServers" more executables, up to "MaxServers".