Internet Information Services (IIS) is the native Windows web server. It supports hosting ASP.NET applications, static websites, PHP, REST APIs, and acts as a reverse proxy. IIS integrates deeply with Windows authentication, the Windows certificate store, and AD.
| Category | Role Service | Purpose |
|---|---|---|
| Web Server → Common HTTP | Default Document, Directory Browsing, HTTP Errors, Static Content | Core static file serving |
| Web Server → Health and Diagnostics | HTTP Logging, Request Monitor, Tracing | Log requests, troubleshoot |
| Web Server → Performance | Static Content Compression, Dynamic Content Compression | Compress responses (Gzip) |
| Web Server → Security | Request Filtering, Basic Authentication, Windows Authentication | Security and auth |
| Web Server → Application Development | ASP.NET 4.8, .NET Extensibility, ISAPI Extensions, CGI | Run web apps |
| Management Tools | IIS Management Console, IIS Management Scripts and Tools | inetmgr GUI + appcmd |
# PowerShell installation — install IIS with all common features Install-WindowsFeature -Name Web-Server -IncludeManagementTools # Install with specific role services Install-WindowsFeature -Name Web-Server, Web-Default-Doc, Web-Dir-Browsing, ` Web-Http-Errors, Web-Static-Content, Web-Http-Logging, Web-Stat-Compression, ` Web-Filtering, Web-Asp-Net45, Web-Net-Ext45, Web-ISAPI-Ext, Web-ISAPI-Filter, ` Web-Mgmt-Console, Web-Scripting-Tools -IncludeManagementTools # Verify IIS is running Get-Service W3SVC | Select-Object Name, Status Invoke-WebRequest http://localhost -UseBasicParsing | Select-Object StatusCode
Open IIS Manager with inetmgr from Run (Win+R) or Start menu.
# IIS AppCmd — command-line management alternative (no GUI needed) # Located at: C:\Windows\System32\inetsrv\appcmd.exe # List all sites appcmd list site # List all app pools appcmd list apppool # List all applications appcmd list app
CompanySiteC:\inetpub\wwwroot\companysite# Create website via PowerShell (WebAdministration module)
Import-Module WebAdministration
# Create the physical directory
New-Item -ItemType Directory -Path "C:\inetpub\sites\companysite" -Force
# Create the website
New-WebSite -Name "CompanySite" `
-PhysicalPath "C:\inetpub\sites\companysite" `
-Port 80 `
-HostHeader "www.company.com" `
-ApplicationPool "CompanySitePool"
# Create the app pool first
New-WebAppPool -Name "CompanySitePool"
# Start the website
Start-WebSite -Name "CompanySite"
Get-WebSite | Select-Object Name, State, PhysicalPath, Bindings
# Grant IIS permission on the folder (ApplicationPoolIdentity)
$acl = Get-Acl "C:\inetpub\sites\companysite"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"IIS AppPool\CompanySitePool", "ReadAndExecute", "ContainerInherit,ObjectInherit", "None", "Allow")
$acl.SetAccessRule($rule)
Set-Acl "C:\inetpub\sites\companysite" $acl
Application Pools provide process isolation between websites. Each pool runs as a separate w3wp.exe process.
| Setting | Options | Recommendation |
|---|---|---|
| .NET CLR Version | v2.0 / v4.0 / No Managed Code | v4.0 for ASP.NET 4.x; "No Managed Code" for PHP, static sites, ASP.NET Core |
| Pipeline Mode | Integrated / Classic | Integrated (default, better performance). Classic only for legacy ASP apps. |
| Identity | NetworkService, LocalSystem, LocalService, ApplicationPoolIdentity, Custom | ApplicationPoolIdentity (default, least privilege). Use custom service account for specific permission needs. |
| Idle Time-out | Default: 20 minutes | Set to 0 for sites that must respond immediately to first request |
| Recycling (time) | Default: 1740 min (29 hrs) | Schedule recycling during off-peak hours instead of a fixed interval |
| Max Worker Processes | Default: 1 | Web Garden: set to >1 to use multiple cores (caution: breaks in-process session state) |
# Configure app pool settings
Set-ItemProperty IIS:\AppPools\CompanySitePool -Name processModel.idleTimeout -Value "00:00:00"
Set-ItemProperty IIS:\AppPools\CompanySitePool -Name recycling.periodicRestart.time -Value "00:00:00"
Set-ItemProperty IIS:\AppPools\CompanySitePool -Name recycling.periodicRestart.schedule -Value @{value="02:00:00"}
# Set app pool to run as a custom service account
Set-ItemProperty IIS:\AppPools\CompanySitePool -Name processModel.userName -Value "LAB\svc-website"
Set-ItemProperty IIS:\AppPools\CompanySitePool -Name processModel.password -Value "P@ssw0rd!"
Set-ItemProperty IIS:\AppPools\CompanySitePool -Name processModel.identityType -Value 3 # 3=SpecificUser
# View all app pools and their state
Get-ChildItem IIS:\AppPools | Select-Object Name, State, @{N="Identity";E={$_.ProcessModel.UserName}}
# Default documents (IIS tries these in order when root URL is requested)
# Default list: Default.htm, Default.asp, index.htm, index.html, iisstart.htm
# View current default documents
Get-WebConfiguration system.webServer/defaultDocument/files "IIS:\Sites\CompanySite"
# Add index.php to default documents
Add-WebConfiguration system.webServer/defaultDocument/files "IIS:\Sites\CompanySite" `
-AtIndex 0 -Value @{value="index.php"}
# Custom error pages — change 404 page
Set-WebConfiguration system.webServer/httpErrors/error[@statusCode='404'] `
"IIS:\Sites\CompanySite" -Value @{path="/errors/404.html"; responseMode="File"}
# web.config approach for custom errors:
# <system.webServer>
# <httpErrors errorMode="Custom">
# <remove statusCode="404" />
# <error statusCode="404" path="/errors/404.html" responseMode="File" />
# </httpErrors>
# </system.webServer>
# Create a self-signed certificate valid for 2 years $cert = New-SelfSignedCertificate ` -DnsName "www.company.com","company.com" ` -CertStoreLocation "cert:\LocalMachine\My" ` -NotAfter (Get-Date).AddYears(2) ` -KeyExportPolicy Exportable ` -KeyAlgorithm RSA ` -KeyLength 2048 # Get the thumbprint $cert.Thumbprint
# Import a certificate from a PFX file (exported from CA or purchased) $pfxPassword = ConvertTo-SecureString "CertPassword!" -AsPlainText -Force Import-PfxCertificate ` -FilePath "C:\Certs\company_com.pfx" ` -CertStoreLocation "cert:\LocalMachine\My" ` -Password $pfxPassword
# Add HTTPS binding using the certificate New-WebBinding -Name "CompanySite" -Protocol "https" -Port 443 -HostHeader "www.company.com" -SslFlags 1 # Assign the certificate to the HTTPS binding $thumbprint = "ABC123DEF456..." # Replace with actual thumbprint $binding = Get-WebBinding -Name "CompanySite" -Protocol "https" $binding.AddSslCertificate($thumbprint, "My") # SslFlags: 0=no SNI, 1=SNI enabled (allows multiple certs on same IP)
# Download win-acme from https://github.com/win-acme/win-acme # Run wacs.exe in an elevated command prompt: wacs.exe # Interactive prompts: # N = New certificate # 2 = Manual input # Enter domain: www.company.com # Validation: http-01 (web server must be accessible on port 80) # win-acme installs the cert and configures auto-renewal via Task Scheduler # Renew manually if needed: wacs.exe --renew --baseuri https://acme-v02.api.letsencrypt.org/
# Method 1: Using Web Platform Installer (WebPI) — easiest # Download WebPI from Microsoft, then: WebPlatformInstaller # GUI tool, search for PHP, click Install # Method 2: Manual PHP installation # 1. Download PHP for Windows (Non-Thread Safe for IIS + FastCGI) from php.net/downloads # 2. Extract to C:\PHP\ # 3. Copy php.ini-production to php.ini and configure: # extension_dir = "C:\PHP\ext" # date.timezone = America/New_York # upload_max_filesize = 64M # post_max_size = 64M # max_execution_time = 300 # 4. In IIS Manager → Server level → Handler Mappings → Add Module Mapping: # Request path: *.php # Module: FastCgiModule # Executable: C:\PHP\php-cgi.exe # Name: PHP via FastCGI # Or via command line (appcmd): appcmd set config /section:system.webServer/fastCgi /+"[fullPath='C:\PHP\php-cgi.exe']" appcmd set config /section:system.webServer/handlers /+"[name='PHP',path='*.php',verb='GET,HEAD,POST',modules='FastCgiModule',scriptProcessor='C:\PHP\php-cgi.exe',resourceType='Either']" # Test PHP echo "<?php phpinfo(); ?>" > C:\inetpub\wwwroot\phpinfo.php # Browse to http://localhost/phpinfo.php
URL Rewrite allows pattern-based redirects and rewrites. Install it from WebPI or the IIS downloads page.
# web.config examples for common rewrite rules
# Rule 1: Redirect HTTP to HTTPS
<!-- <system.webServer><rewrite><rules> -->
<rule name="Redirect HTTP to HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
# Rule 2: Redirect www to non-www (canonical URL)
<rule name="Remove WWW" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTP_HOST}" pattern="^www\.company\.com$" />
</conditions>
<action type="Redirect" url="https://company.com/{R:1}" redirectType="Permanent" />
</rule>
# Rule 3: Clean URLs (remove .php extension)
<rule name="Clean URLs" stopProcessing="true">
<match url="^([^.]+)$" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}.php" matchType="IsFile" />
</conditions>
<action type="Rewrite" url="{R:1}.php" />
</rule>
web.config is an XML file placed in the website root that overrides IIS settings for that site or application. It is hot-reloadable — changes apply immediately without restarting IIS.
<!-- Example web.config for a PHP/static website -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<!-- Default documents -->
<defaultDocument>
<files>
<clear />
<add value="index.php" />
<add value="index.html" />
</files>
</defaultDocument>
<!-- Custom errors -->
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" path="/404.html" responseMode="ExecuteURL" />
<error statusCode="500" path="/500.html" responseMode="ExecuteURL" />
</httpErrors>
<!-- Security: remove server header, hide version -->
<security>
<requestFiltering removeServerHeader="true">
<requestLimits maxAllowedContentLength="67108864" /> <!-- 64MB -->
</requestFiltering>
</security>
<!-- Enable compression -->
<urlCompression doStaticCompression="true" doDynamicCompression="true" />
<!-- HTTP headers -->
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
ARR + URL Rewrite turns IIS into a reverse proxy — it receives requests and forwards them to backend servers. Install ARR from WebPI.
# Enable proxy in ARR (must be done once at server level)
# IIS Manager → Server → Application Request Routing → Server Proxy Settings
# Check "Enable proxy" → Apply
# Reverse proxy web.config rule:
# Forward all requests to a backend Node.js app running on port 3000
<rewrite>
<rules>
<rule name="ReverseProxy to Node" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://localhost:3000/{R:1}" />
</rule>
</rules>
</rewrite>
# Forward to backend API server (different machine)
<rule name="API Proxy" stopProcessing="true">
<match url="^api/(.*)" />
<action type="Rewrite" url="http://192.168.1.50:8080/api/{R:1}" />
</rule>
# Default log location
C:\inetpub\logs\LogFiles\W3SVC1\ # Site ID 1 (Default Web Site)
C:\inetpub\logs\LogFiles\W3SVC2\ # Site ID 2 (your custom site)
# Log file naming: u_exYYMMDD.log (e.g., u_ex240115.log)
# Format: W3C Extended Log Format
# Fields: date time cs-method cs-uri-stem cs-uri-query s-port c-ip sc-status time-taken
# Parse logs with PowerShell (find all 500 errors today)
$logFile = "C:\inetpub\logs\LogFiles\W3SVC1\u_ex$(Get-Date -Format 'yyMMdd').log"
Get-Content $logFile | Where-Object {$_ -notlike "#*"} |
ConvertFrom-Csv -Delimiter ' ' -Header date,time,sip,method,path,query,sport,cip,agent,refer,status,substatus,winstatus,timetaken |
Where-Object {$_.status -eq "500"} |
Select-Object date,time,method,path,status,timetaken | Format-Table
| Error | Cause | Fix |
|---|---|---|
| 403.14 Forbidden | Directory listing disabled, no default document | Add default document, or enable directory browsing |
| 403.18 Forbidden | Cannot execute CGI in this application pool | Check app pool pipeline mode — switch to Classic for old CGI apps |
| 404 Not Found | File missing, URL routing issue | Verify file exists; check URL Rewrite rules; check handler mappings |
| 500.19 Internal Server Error | web.config syntax error or duplicate keys | Validate web.config XML; check IIS feature is installed for the config element used |
| 500.21 | .NET module not loaded or handler not found | Ensure ASP.NET is installed; run aspnet_regiis -i |
| 503 Service Unavailable | App pool stopped or disabled | Start the app pool in IIS Manager; check Event Log for crash reason |
# Useful IIS PowerShell diagnostics
# Check all sites and their status
Get-WebSite | Select-Object Name, State, PhysicalPath
# Check all app pools
Get-WebConfiguration system.applicationHost/applicationPools/add |
Select-Object name, state, @{N="Identity";E={$_.processModel.userName}}
# Recycle an app pool manually
Restart-WebAppPool -Name "CompanySitePool"
# Test IIS configuration validity (always run before big changes)
appcmd validate config
# Reset IIS (use sparingly — drops all active connections)
iisreset /noforce
# Start/stop individual site
Start-WebSite -Name "CompanySite"
Stop-WebSite -Name "CompanySite"
You can now install and configure IIS, create websites and app pools, set up HTTPS with real or self-signed certificates, configure URL Rewrite rules, host PHP applications, and diagnose common IIS errors.