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.exe location
  • The Visual C++ Redistributable required by your PHP build is installed (Get-Item "HKLM:SOFTWAREMicrosoftVisualStudio*VCRuntimesx64")
  • The PHP error log at C:inetpublogsphp_errors.log for extension load failures

HTTP 500.19 — Configuration Error means IIS cannot read or parse web.config. Common causes:

  • A web.config section 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 (grant IIS_IUSRS read 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.