How to Configure IIS with FastCGI for PHP on Windows Server 2025
PHP is one of the most widely deployed server-side languages in the world, powering platforms such as WordPress, Drupal, and Laravel. While Linux with PHP-FPM is the dominant hosting environment, Windows Server 2025 with IIS and FastCGI is a fully capable alternative — particularly for organizations already invested in Microsoft infrastructure. This tutorial explains the difference between CGI and FastCGI, walks through installing the CGI feature on IIS, configuring a FastCGI application pool for php-cgi.exe, mapping the .php extension to the handler, tuning php.ini for IIS, and troubleshooting the most common HTTP 500 errors that arise during PHP setup.
Prerequisites
- Windows Server 2025 with IIS 10 installed
- A downloaded PHP build for Windows — use the Non-Thread-Safe (NTS) x64 ZIP from windows.php.net (NTS is required for FastCGI; Thread-Safe is for the deprecated ISAPI handler)
- The Visual C++ Redistributable matching your PHP build version (e.g., VC16 for PHP 8.3)
- PowerShell 5.1 or later running as Administrator
- The WebAdministration PowerShell module (installed with IIS management tools)
Step 1: CGI vs FastCGI — Why It Matters
Traditional CGI (Common Gateway Interface) spawns a new process for every HTTP request. For PHP this means loading the PHP interpreter, parsing php.ini, initializing extensions, and running the script — on every single request. Under even moderate load this is extremely inefficient, and process-spawn overhead becomes the dominant bottleneck.
FastCGI addresses this by keeping a pool of persistent php-cgi.exe processes alive between requests. IIS communicates with them over a named pipe or socket, passing request data in the FastCGI protocol. Because the processes are reused, PHP startup overhead is paid only once per process lifetime (or after PHP_FCGI_MAX_REQUESTS requests to guard against memory leaks). The result is throughput comparable to mod_php on Apache.
On Windows, PHP-FPM (FastCGI Process Manager, the preferred method on Linux) is not available. The Windows equivalent is running php-cgi.exe under IIS’s FastCGI module, which manages the process pool for you.
Step 2: Installing the CGI Feature on IIS
# Install IIS CGI feature (enables FastCGI support)
Install-WindowsFeature Web-CGI -IncludeManagementTools
# Verify installation
Get-WindowsFeature -Name Web-CGI
# Expected output:
# Display Name Name Install State
# ------------ ---- -------------
# [X] CGI Web-CGI Installed
No restart is required after installing this feature. The IIS FastCGI module (FastCgiModule) becomes available immediately and can be verified in IIS Manager under Modules.
Step 3: Installing PHP
# Extract the PHP NTS ZIP to a stable location
$phpPath = "C:PHP"
New-Item -ItemType Directory -Path $phpPath -Force
# Replace the URL with the latest PHP 8.3 NTS x64 ZIP from windows.php.net
Expand-Archive -Path "$env:USERPROFILEDownloadsphp-8.3.x-nts-Win32-vs16-x64.zip" `
-DestinationPath $phpPath -Force
# Create php.ini from the recommended production template
Copy-Item "$phpPathphp.ini-production" "$phpPathphp.ini"
# Add PHP to the system PATH so other tools can call it
$envPath = [Environment]::GetEnvironmentVariable("Path","Machine")
if ($envPath -notlike "*$phpPath*") {
[Environment]::SetEnvironmentVariable("Path", "$envPath;$phpPath", "Machine")
}
# Quick sanity check (open a new PowerShell window after PATH update)
php -v
Step 4: Configuring FastCGI in IIS
The FastCGI application definition tells IIS how to manage php-cgi.exe processes. The settings below are configured at the server level (applying to all sites) using PowerShell’s Add-WebConfiguration:
Import-Module WebAdministration
$phpCgi = "C:PHPphp-cgi.exe"
# Add a FastCGI application entry
Add-WebConfiguration -PSPath "IIS:" `
-Filter "system.webServer/fastCgi" `
-Value @{
fullPath = $phpCgi
maxInstances = 0 # 0 = auto (number of logical CPUs)
instanceMaxRequests = 10000 # recycle process after this many requests (prevents memory leaks)
activityTimeout = 70 # seconds of inactivity before killing an idle process
requestTimeout = 90 # maximum seconds a single request may run
protocol = "NamedPipe" # or "Tcp" if using TCP sockets
idleTimeout = 300
}
# Set environment variables for php-cgi.exe
# PHP_FCGI_MAX_REQUESTS should match instanceMaxRequests
$filter = "system.webServer/fastCgi/application[@fullPath='$phpCgi']/environmentVariables"
Add-WebConfiguration -PSPath "IIS:" -Filter $filter -Value @{
name = "PHP_FCGI_MAX_REQUESTS"
value = "10000"
}
Add-WebConfiguration -PSPath "IIS:" -Filter $filter -Value @{
name = "PHPRC"
value = "C:PHP" # Directory containing php.ini
}
The maxInstances = 0 setting tells IIS to automatically set the pool size to the number of logical CPU cores, which is a good default. For high-traffic sites, benchmark and increase this value.
Step 5: Adding a Handler Mapping for .php Files
Without a handler mapping, IIS does not know to pass .php requests to FastCGI — it will either download the file or return a 404.
$siteName = "Default Web Site"
$phpCgi = "C:PHPphp-cgi.exe"
# Add handler mapping for .php at the site level
Add-WebConfiguration -PSPath "IIS:Sites$siteName" `
-Filter "system.webServer/handlers" `
-Value @{
name = "PHP_via_FastCGI"
path = "*.php"
verb = "GET,HEAD,POST,PUT,DELETE,OPTIONS"
modules = "FastCgiModule"
scriptProcessor = $phpCgi
resourceType = "File"
requireAccess = "Script"
}
# Verify the mapping was added
Get-WebHandler -PSPath "IIS:Sites$siteName" | Where-Object { $_.Name -eq "PHP_via_FastCGI" }
Alternatively, set the mapping globally at the server level (-PSPath "IIS:") so it applies to all sites on the server.
Step 6: Configuring php.ini for IIS
Open C:PHPphp.ini in a text editor and apply these IIS-specific settings:
; Set the extension directory — required for extensions to load
extension_dir = "C:PHPext"
; Enable common extensions
extension=curl
extension=gd
extension=mbstring
extension=mysqli
extension=openssl
extension=pdo_mysql
extension=pdo_sqlite
extension=sqlite3
; IIS-specific: set the correct temp paths (avoid writing to system temp)
sys_temp_dir = "C:PHPtemp"
upload_tmp_dir = "C:PHPtemp"
session.save_path = "C:PHPsessions"
; Logging
log_errors = On
error_log = "C:inetpublogsphp_errors.log"
display_errors = Off ; NEVER On in production
; Limits
max_execution_time = 60
memory_limit = 256M
upload_max_filesize = 32M
post_max_size = 34M
; FastCGI-specific: output buffering must be enabled for FastCGI
output_buffering = On
implicit_flush = Off
; Date timezone (avoid warnings)
date.timezone = "America/New_York"
Create the temp and sessions directories and grant the IIS application pool identity write access:
New-Item -ItemType Directory -Path "C:PHPtemp","C:PHPsessions" -Force
# Grant IIS_IUSRS write access (used by the DefaultAppPool and most custom pools)
icacls "C:PHPtemp" /grant "IIS_IUSRS:(OI)(CI)M" /T
icacls "C:PHPsessions" /grant "IIS_IUSRS:(OI)(CI)M" /T
icacls "C:inetpublogs" /grant "IIS_IUSRS:(OI)(CI)M" /T
Step 7: Testing the PHP Configuration
Create a test file in your site root — delete it after testing, as it exposes server configuration:
# Create phpinfo test page (delete after confirming PHP works)
Set-Content -Path "C:inetpubwwwrootphptest.php" -Value "<?php phpinfo(); ?>"
Navigate to http://localhost/phptest.php. If PHP is working correctly, you will see the PHP information page showing the version, loaded extensions, and INI settings. After confirming, delete the file:
Remove-Item "C:inetpubwwwrootphptest.php" -Force
Step 8: Troubleshooting HTTP 500 Errors
HTTP 500.0 — Internal Server Error usually means php-cgi.exe crashed or could not be found at the configured path. Check:
- The path in the FastCGI application and handler mapping exactly matches the actual
php-cgi.exelocation - The Visual C++ Redistributable required by your PHP build is installed (
Get-Item "HKLM:SOFTWAREMicrosoftVisualStudio*VCRuntimesx64") - The PHP error log at
C:inetpublogsphp_errors.logfor extension load failures
HTTP 500.19 — Configuration Error means IIS cannot read or parse web.config. Common causes:
- A
web.configsection that requires a feature not installed in IIS (check the Config Source in the error detail) - Invalid XML syntax in
web.config - Permission denied reading
web.config(grantIIS_IUSRSread access to the site root)
# Check IIS error details (enable detailed errors during troubleshooting)
Set-WebConfigurationProperty `
-PSPath "IIS:SitesDefault Web Site" `
-Filter "system.webServer/httpErrors" `
-Name errorMode -Value "Detailed"
# Review the Windows Application event log for FastCGI-related errors
Get-EventLog -LogName Application -Source "IIS-FastCGI" -Newest 20 |
Select-Object TimeGenerated, EntryType, Message | Format-List
Conclusion
Hosting PHP on IIS with FastCGI on Windows Server 2025 delivers performance comparable to Linux-based deployments, with the operational advantages of Windows Server management tooling and Active Directory integration. The key steps — installing the CGI feature, defining the FastCGI application pool with appropriate process limits, mapping the .php extension to php-cgi.exe, and tuning php.ini temp paths and error logging — set a stable foundation for running PHP applications at scale. Once your baseline configuration is verified with phpinfo(), layer on application-specific settings such as OPcache for bytecode caching (opcache.enable=1), which can reduce PHP response times by 40–80% for content-heavy applications like WordPress.