How to Install and Configure PHP in IIS on Windows Server 2022

Running PHP on IIS with Windows Server 2022 is a fully supported and production-ready configuration. IIS handles PHP through FastCGI, which provides better performance and isolation compared to older CGI methods. This guide walks through a complete PHP installation, IIS handler configuration, php.ini tuning, extension management, and OPcache setup for production deployments.

Downloading PHP for Windows

PHP for Windows is available from https://windows.php.net/download/. Always download the Non-Thread Safe (NTS) version for use with FastCGI on IIS. The Thread Safe (TS) variant is for mod_php with Apache and will not function correctly with IIS FastCGI. Choose the x64 version for 64-bit IIS. As of 2024, PHP 8.2 and 8.3 are the current stable branches.

Download and extract the PHP zip archive to a permanent location:

# Download PHP 8.3 NTS x64 (adjust URL to current version)
$phpVersion = "8.3.7"
$phpUrl = "https://windows.php.net/downloads/releases/php-$phpVersion-nts-Win32-vs16-x64.zip"
$phpDest = "C:PHP"

Invoke-WebRequest -Uri $phpUrl -OutFile "C:php.zip"
Expand-Archive -Path "C:php.zip" -DestinationPath $phpDest -Force
Remove-Item "C:php.zip"

PHP requires the Visual C++ Redistributable that matches the version used to compile it. PHP 8.x uses VS16 (Visual Studio 2019/2022). Download and install the matching VC++ runtime from Microsoft if not already present:

# Install VC++ Redistributable 2019/2022 x64 (required for PHP 8.x VS16)
Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vc_redist.x64.exe" -OutFile "C:vc_redist.x64.exe"
Start-Process "C:vc_redist.x64.exe" -ArgumentList "/quiet /norestart" -Wait

Configuring php.ini

PHP ships with two sample configuration files: php.ini-development and php.ini-production. Copy the production version to create the active configuration:

Copy-Item "C:PHPphp.ini-production" "C:PHPphp.ini"

Edit C:PHPphp.ini and make the following key changes for a production IIS environment:

; Set the extension directory path (required for extensions to load)
extension_dir = "C:PHPext"

; Set the timezone
date.timezone = "America/New_York"

; Set upload and post limits
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120
max_input_time = 120
memory_limit = 256M

; Error logging (production — no display, log to file)
display_errors = Off
log_errors = On
error_log = "C:PHPlogsphp_errors.log"

; Session settings
session.save_path = "C:PHPtmp"

; Enable required extensions (uncomment lines in php.ini)
extension=curl
extension=fileinfo
extension=gd
extension=intl
extension=mbstring
extension=mysqli
extension=openssl
extension=pdo_mysql
extension=pdo_sqlite
extension=soap
extension=zip

Create the log and temp directories and set permissions:

New-Item -Path "C:PHPlogs" -ItemType Directory -Force
New-Item -Path "C:PHPtmp" -ItemType Directory -Force

# Grant IIS_IUSRS write access to logs and temp
icacls "C:PHPlogs" /grant "IIS_IUSRS:(OI)(CI)M"
icacls "C:PHPtmp" /grant "IIS_IUSRS:(OI)(CI)M"

Ensuring FastCGI Is Installed in IIS

FastCGI must be installed as an IIS feature before PHP can be configured as a handler. Verify and install it:

Install-WindowsFeature -Name Web-CGI

# Verify
Get-WindowsFeature -Name Web-CGI | Select-Object Name, InstallState

Adding PHP as a FastCGI Handler in IIS

Register the PHP executable with IIS FastCGI and then add a handler mapping so IIS passes .php requests to PHP:

# Register PHP with IIS FastCGI
$phpExe = "C:PHPphp-cgi.exe"
Add-WebConfiguration -Filter "system.webServer/fastCgi" -Value @{fullPath=$phpExe}

# Add handler mapping for .php files
$handlerParams = @{
    Name    = "PHP_via_FastCGI"
    Path    = "*.php"
    Verb    = "*"
    Modules = "FastCgiModule"
    ScriptProcessor = $phpExe
    ResourceType = "File"
}
Add-WebHandler @handlerParams -PSPath "IIS:"

Alternatively, use appcmd.exe for handler registration (classic approach):

cd C:WindowsSystem32inetsrv

appcmd.exe set config -section:system.webServer/fastCgi /+"[fullPath='C:PHPphp-cgi.exe']"

appcmd.exe set config -section:system.webServer/handlers /+"[name='PHP_FastCGI',path='*.php',verb='GET,HEAD,POST',modules='FastCgiModule',scriptProcessor='C:PHPphp-cgi.exe',resourceType='File']"

Add index.php to the default document list so IIS serves it when no filename is specified:

Add-WebConfiguration -Filter "system.webServer/defaultDocument/files" -Value @{value="index.php"} -PSPath "IIS:"

Configuring FastCGI Settings

FastCGI settings control how IIS manages PHP worker processes. These settings are critical for performance and stability under load:

# Configure FastCGI process pool settings
Set-WebConfigurationProperty -Filter "system.webServer/fastCgi/application[@fullPath='C:PHPphp-cgi.exe']" -Name "maxInstances" -Value 50
Set-WebConfigurationProperty -Filter "system.webServer/fastCgi/application[@fullPath='C:PHPphp-cgi.exe']" -Name "activityTimeout" -Value 70
Set-WebConfigurationProperty -Filter "system.webServer/fastCgi/application[@fullPath='C:PHPphp-cgi.exe']" -Name "requestTimeout" -Value 90
Set-WebConfigurationProperty -Filter "system.webServer/fastCgi/application[@fullPath='C:PHPphp-cgi.exe']" -Name "idleTimeout" -Value 300
Set-WebConfigurationProperty -Filter "system.webServer/fastCgi/application[@fullPath='C:PHPphp-cgi.exe']" -Name "instanceMaxRequests" -Value 10000

Key settings explained:

maxInstances — maximum number of concurrent php-cgi.exe processes. Set to the number of concurrent PHP requests you expect to handle. On a dedicated PHP server, this is typically 20–100 depending on available RAM.

instanceMaxRequests — number of requests a single php-cgi.exe process handles before being recycled. This prevents PHP memory leaks from accumulating. 10000 is a sensible default.

activityTimeout and requestTimeout — timeouts for process activity and total request processing. Set these to slightly more than your max_execution_time in php.ini.

Testing PHP with phpinfo()

Create a test PHP file in your site’s web root to verify PHP is working:

Set-Content "C:inetpubwwwrootphpinfo.php" ""

Navigate to http://localhost/phpinfo.php in a browser. You should see the PHP information page showing the version, configuration, loaded extensions, and environment variables. Verify that the configuration file path shows C:PHPphp.ini and that your enabled extensions appear in the extension list.

Important: Delete or restrict this file after testing — it exposes sensitive server configuration information.

Remove-Item "C:inetpubwwwrootphpinfo.php"

Installing PHP Extensions

PHP extensions are DLL files located in C:PHPext. Extensions bundled with the PHP download are enabled by uncommenting the corresponding line in php.ini. For extensions not included (like imagick, redis, sqlsrv, etc.), download the matching DLL from PECL (https://pecl.php.net) or pre-compiled Windows binaries.

Installing the Microsoft SQL Server driver for PHP:

# Download SQLSRV extension from Microsoft
# https://learn.microsoft.com/en-us/sql/connect/php/download-drivers-php-sql-server
# Extract and copy DLL files to C:PHPext

# Add to php.ini:
# extension=php_sqlsrv_83_nts_x64.dll
# extension=php_pdo_sqlsrv_83_nts_x64.dll

# Verify extension loaded after IIS restart
php -m | findstr sqlsrv

Installing the ImageMagick extension (Imagick) requires both the Imagick DLL and the ImageMagick binaries:

# 1. Install ImageMagick for Windows from https://imagemagick.org/script/download.php
# 2. Download matching php_imagick.dll from PECL or Shiftleft
# 3. Copy php_imagick.dll to C:PHPext
# 4. Add to php.ini:
# extension=imagick
# 5. Ensure ImageMagick's bin directory is in the system PATH
[System.Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:Program FilesImageMagick-7.1.1-Q16-HDRI", "Machine")

OPcache Configuration

OPcache is bundled with PHP 8.x and dramatically improves PHP performance by caching compiled bytecode in shared memory. Without OPcache, PHP parses and compiles every .php file on every request. Enable and configure it in php.ini:

; Enable OPcache (already present in php.ini-production, may need to be uncommented)
zend_extension=opcache

[opcache]
; Enable OPcache
opcache.enable=1

; Enable in CLI (for scripts and cron jobs)
opcache.enable_cli=0

; Memory for storing cached bytecode (in MB)
opcache.memory_consumption=256

; Memory for interned strings (shared across requests)
opcache.interned_strings_buffer=32

; Maximum number of files that can be cached
opcache.max_accelerated_files=20000

; Check file timestamps for changes every N seconds (0 = never, revalidate_freq ignored)
; In production, set to 0 and manually clear cache on deployment
opcache.validate_timestamps=0
opcache.revalidate_freq=0

; JIT compiler (PHP 8.0+) - "tracing" is best for most web apps
opcache.jit=tracing
opcache.jit_buffer_size=128M

After making php.ini changes, restart the IIS FastCGI worker processes to apply the new configuration:

iisreset /noforce

# Or restart just the application pool
Restart-WebAppPool -Name "DefaultAppPool"

Verify OPcache is active with a quick PHP CLI check:

C:PHPphp.exe -r "echo opcache_get_status()['opcache_enabled'] ? 'OPcache ON' : 'OPcache OFF';"

Performance Tuning Summary

For a production IIS/PHP deployment, apply these additional performance measures:

; php.ini — disable unused functionality
; Disable assertions in production
zend.assertions=-1

; Disable PHP version exposure
expose_php = Off

; Limit error reporting to critical errors only
error_reporting = E_ERROR | E_WARNING | E_PARSE

; Increase realpath cache for large applications
realpath_cache_size=4096K
realpath_cache_ttl=600

Also enable IIS static content compression and dynamic content compression to reduce transfer sizes. For WordPress, Drupal, or Laravel applications, configure IIS output caching for static assets. Always use a dedicated application pool per PHP site, and configure the pool’s recycling settings to prevent memory accumulation over long uptime periods.