How to Configure IIS with FastCGI for PHP on Windows Server 2022

IIS on Windows Server 2022 runs PHP through the FastCGI protocol, which provides better performance and stability than the older CGI interface. FastCGI maintains a pool of persistent PHP processes that handle requests without the overhead of spawning a new process for every request. This guide covers installing the CGI feature, configuring PHP FastCGI handlers, tuning FastCGI settings, running multiple PHP versions, and managing FastCGI process lifecycles on IIS.

Installing the CGI Feature in IIS

IIS does not include the CGI/FastCGI support by default—it must be added as a role service. Install it using Server Manager or the Install-WindowsFeature cmdlet. The feature name is Web-CGI.

# Install IIS with the CGI feature (required for FastCGI)
Install-WindowsFeature -Name Web-CGI -IncludeManagementTools

# If IIS is not yet installed, install it with CGI and common features
Install-WindowsFeature -Name Web-Server, Web-CGI, Web-Mgmt-Tools `
    -IncludeManagementTools

# Verify the CGI feature is enabled
Get-WindowsFeature Web-CGI

After installing the CGI feature, the FastCGI module (fastcgi.dll) is registered in IIS and available for configuration. You can confirm it is active by checking the global modules list in IIS Manager or via appcmd:

# Confirm FastCGI module is registered
%windir%system32inetsrvappcmd.exe list module FastCgiModule

Downloading and Installing PHP

Download the PHP Non-Thread-Safe (NTS) x64 build from https://windows.php.net/download. The NTS build is required for IIS FastCGI—the Thread-Safe (TS) build is for the PHP ISAPI module which is a different integration method. Extract PHP to a fixed path such as C:PHP8.3.

# Create the PHP directory
New-Item -Path "C:PHP8.3" -ItemType Directory -Force

# After downloading and extracting the ZIP to C:PHP8.3,
# copy the default php.ini template
Copy-Item "C:PHP8.3php.ini-production" "C:PHP8.3php.ini"

# Add PHP to the system PATH (optional but recommended)
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "Machine")
[Environment]::SetEnvironmentVariable("PATH", "$currentPath;C:PHP8.3", "Machine")

# Verify PHP runs from command line
C:PHP8.3php.exe -v

Configuring the PHP FastCGI Handler in IIS

The FastCGI handler must be registered globally in IIS and then mapped as a handler for .php files. Register the FastCGI application first, then add the handler mapping. This can be done in IIS Manager or via appcmd/PowerShell.

# Register PHP as a FastCGI application at the server level
# This adds the entry to applicationHost.config
%windir%system32inetsrvappcmd.exe set config -section:system.webServer/fastCgi `
    /+[fullPath='C:PHP8.3php-cgi.exe']

# Add the .php handler mapping for the Default Web Site
%windir%system32inetsrvappcmd.exe set config "Default Web Site" `
    -section:system.webServer/handlers `
    /+[name='PHP_via_FastCGI',path='*.php',verb='GET,HEAD,POST',`
       modules='FastCgiModule',scriptProcessor='C:PHP8.3php-cgi.exe',`
       resourceType='Either']

The equivalent web.config approach (for per-site or per-directory configuration) looks like this:



  
    
      
    
  

FastCGI Settings: instanceMaxRequests, maxInstances, monitorChangesTo

FastCGI application settings control how many PHP processes are maintained and how they behave. These settings are configured at the server level in applicationHost.config:

# Set FastCGI application settings for PHP
%windir%system32inetsrvappcmd.exe set config -section:system.webServer/fastCgi `
    /[fullPath='C:PHP8.3php-cgi.exe'].instanceMaxRequests:10000 `
    /[fullPath='C:PHP8.3php-cgi.exe'].maxInstances:8 `
    /[fullPath='C:PHP8.3php-cgi.exe'].monitorChangesTo:'C:PHP8.3php.ini' `
    /[fullPath='C:PHP8.3php-cgi.exe'].activityTimeout:300 `
    /[fullPath='C:PHP8.3php-cgi.exe'].requestTimeout:90 `
    /[fullPath='C:PHP8.3php-cgi.exe'].idleTimeout:300

instanceMaxRequests: The number of requests a single php-cgi.exe process will handle before it is automatically recycled. The default is 200. Increasing this to 10000 reduces the overhead of frequent process recycling, but means memory leaks in PHP extensions accumulate longer. A value between 5000 and 10000 is common for production.

maxInstances: The maximum number of concurrent php-cgi.exe processes FastCGI will spawn. Setting it to 0 means unlimited (limited only by system resources). A value of 0 is appropriate for high-traffic sites. Setting a specific number caps the concurrent PHP processes—useful on servers with limited RAM. Each php-cgi.exe process typically uses 30–80 MB of RAM depending on the application.

monitorChangesTo: When this file changes on disk, FastCGI recycles all PHP processes. Setting it to php.ini means that any change to php.ini takes effect without requiring an IIS restart—FastCGI detects the timestamp change and gracefully restarts the PHP processes.

php.ini Tuning for IIS FastCGI

Several php.ini settings are particularly important for IIS/FastCGI deployments. Open C:PHP8.3php.ini and configure:

; Error logging - log to file, do not display errors in production
log_errors = On
error_log = C:inetpublogsphpphp_errors.log
display_errors = Off
display_startup_errors = Off

; Timezone (required for date functions)
date.timezone = America/New_York

; Session storage (use a path IIS app pool identity can write to)
session.save_path = C:WindowsTemp

; File upload settings
upload_max_filesize = 64M
post_max_size = 64M
upload_tmp_dir = C:WindowsTemp

; Execution limits
max_execution_time = 60
memory_limit = 256M

; Required extension directory
extension_dir = "C:PHP8.3ext"

; Common extensions to enable (uncomment as needed)
extension=curl
extension=gd
extension=mbstring
extension=mysqli
extension=openssl
extension=pdo_mysql
extension=pdo_sqlite
extension=sqlite3
extension=zip

; CGI settings for IIS FastCGI
cgi.force_redirect = 0
cgi.fix_pathinfo = 1
fastcgi.impersonate = 1

The fastcgi.impersonate = 1 setting is critical for IIS—it allows PHP to impersonate the security token of the calling user, which is required for Windows authentication scenarios and for proper NTFS permission enforcement when PHP writes files.

Enabling OPcache in FastCGI

OPcache dramatically improves PHP performance by caching compiled PHP bytecode in memory, eliminating the need to reparse PHP files on every request. Enable it in php.ini:

; OPcache settings (add to php.ini)
zend_extension=opcache

[opcache]
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.jit=tracing
opcache.jit_buffer_size=64M

opcache.revalidate_freq=60 means OPcache checks whether a PHP file has changed on disk every 60 seconds. On production servers where PHP files do not change during normal operation, setting this to a higher value (or 0 with opcache.validate_timestamps=0) further reduces filesystem overhead. The JIT compiler (available since PHP 8.0) provides additional performance gains for CPU-bound PHP code but has minimal impact on typical web I/O-bound applications.

Testing PHP in IIS

Create a test file to verify the PHP FastCGI configuration is working. Never leave phpinfo() files accessible in production—use them only during initial setup and delete them immediately after.

# Create a phpinfo test file (delete after verification!)
$phpInfoContent = ''
$phpInfoContent | Out-File -FilePath "C:inetpubwwwrootinfo.php" -Encoding UTF8

# Request the file through IIS
$response = Invoke-WebRequest -Uri "http://localhost/info.php" -UseBasicParsing
if ($response.Content -match "PHP Version") {
    Write-Host "PHP FastCGI is working correctly"
    Write-Host ($response.Content | Select-String "PHP Version.*" | Select-Object -First 1)
}

# Delete the test file immediately after verification
Remove-Item "C:inetpubwwwrootinfo.php"

Multiple PHP Versions in FastCGI

IIS FastCGI supports running multiple PHP versions simultaneously on different sites or virtual directories. Each version is registered as a separate FastCGI application and mapped to a different site or directory handler.

# Install PHP 8.1 alongside PHP 8.3
New-Item -Path "C:PHP8.1" -ItemType Directory -Force
# Extract PHP 8.1 NTS to C:PHP8.1 and configure php.ini there

# Register PHP 8.1 as a FastCGI application
%windir%system32inetsrvappcmd.exe set config -section:system.webServer/fastCgi `
    /+[fullPath='C:PHP8.1php-cgi.exe']

# Assign PHP 8.1 handler to a specific site ("LegacyApp")
%windir%system32inetsrvappcmd.exe set config "LegacyApp" `
    -section:system.webServer/handlers `
    /+[name='PHP81_via_FastCGI',path='*.php',verb='GET,HEAD,POST',`
       modules='FastCgiModule',scriptProcessor='C:PHP8.1php-cgi.exe',`
       resourceType='Either']

# List all registered FastCGI applications
%windir%system32inetsrvappcmd.exe list config -section:system.webServer/fastCgi

PHP Manager for IIS

PHP Manager for IIS is a free IIS Manager extension that provides a GUI for registering PHP versions, editing php.ini, checking configuration recommendations, and managing PHP handler mappings per site. Download it from https://www.iis.net/downloads/community/2018/05/php-manager-150-for-iis-10.

# Install PHP Manager silently
msiexec /i PHPManagerForIIS_V1.5.0.msi /quiet /norestart

# Restart IIS Manager to see the PHP Manager module
iisreset /noforce

# PHP Manager adds a "PHP Manager" icon in IIS Manager at server level and site level
# It allows clicking through to:
# - Register new PHP version
# - Set default PHP version per site
# - Check and fix php.ini recommendations
# - Enable/disable PHP extensions via GUI

PHP Error Logging on IIS

PHP errors go to the file specified by error_log in php.ini. Ensure the directory exists and the IIS application pool identity (typically IIS AppPoolDefaultAppPool or a custom identity) has write permission to that path:

# Create the PHP log directory
New-Item -Path "C:inetpublogsphp" -ItemType Directory -Force

# Grant write permission to the app pool identity
$acl = Get-Acl "C:inetpublogsphp"
$identity = "IIS AppPoolDefaultAppPool"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    $identity, "Write,Modify", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$acl.SetAccessRule($rule)
Set-Acl -Path "C:inetpublogsphp" -AclObject $acl

# Tail the PHP error log
Get-Content "C:inetpublogsphpphp_errors.log" -Wait -Tail 20

IIS also captures stderr output from php-cgi.exe in the IIS logs. If php-cgi.exe crashes entirely, the FastCGI module logs the exit code and a 500.13 error (CGI application crashed) or 500.52 (rewrite error). Check the Event Viewer under Windows Logs → Application for FastCGI-related events with source IIS FastCGI Module.

Recycling FastCGI Processes

FastCGI processes are recycled automatically based on the instanceMaxRequests counter. They can also be recycled on-demand by restarting the IIS worker process or the entire web service. Restarting just the application pool is the least disruptive option:

# Recycle (restart) a specific application pool
# This terminates and restarts the w3wp.exe and all associated php-cgi.exe processes
Import-Module WebAdministration
Restart-WebAppPool -Name "DefaultAppPool"

# Stop and start all FastCGI processes by restarting the site
Stop-Website "Default Web Site"
Start-Website "Default Web Site"

# Graceful IIS restart (does not drop existing connections immediately)
iisreset /noforce

# Hard IIS restart (immediately recycles everything)
iisreset

When monitorChangesTo is set in the FastCGI application configuration, FastCGI automatically recycles PHP processes when that file’s last-write timestamp changes. Touching the php.ini file from the command line triggers this without any manual restart:

# Force FastCGI to recycle all PHP processes by touching php.ini
(Get-Item "C:PHP8.3php.ini").LastWriteTime = Get-Date
Write-Host "Touched php.ini - FastCGI will recycle PHP processes on next request"

This is particularly useful during deployments where php.ini has changed or after installing a new PHP extension—it ensures the new configuration is picked up without a full IIS service restart.