🏠 Home / Hub

Lesson 4 — IIS Web Server

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.

1. Installing IIS

Via Server Manager

  1. Server Manager → Manage → Add Roles and Features
  2. Role-based installation → select server
  3. Check Web Server (IIS) → Accept additional features
  4. Role Services — select what you need (see table below)
  5. Install

Recommended Role Services to Select

CategoryRole ServicePurpose
Web Server → Common HTTPDefault Document, Directory Browsing, HTTP Errors, Static ContentCore static file serving
Web Server → Health and DiagnosticsHTTP Logging, Request Monitor, TracingLog requests, troubleshoot
Web Server → PerformanceStatic Content Compression, Dynamic Content CompressionCompress responses (Gzip)
Web Server → SecurityRequest Filtering, Basic Authentication, Windows AuthenticationSecurity and auth
Web Server → Application DevelopmentASP.NET 4.8, .NET Extensibility, ISAPI Extensions, CGIRun web apps
Management ToolsIIS Management Console, IIS Management Scripts and Toolsinetmgr 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

2. IIS Manager Overview

Open IIS Manager with inetmgr from Run (Win+R) or Start menu.

Navigation Tree Structure

# 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

3. Create a New Website

Via IIS Manager

  1. In IIS Manager, right-click SitesAdd Website
  2. Site name: e.g., CompanySite
  3. Application pool: create new or select existing
  4. Physical path: e.g., C:\inetpub\wwwroot\companysite
  5. Binding: Type=HTTP, IP=All Unassigned (or specific IP), Port=80, Host name=www.company.com
  6. Click OK — site starts automatically
Ensure the physical path exists and IIS has read permission. The IIS AppPool identity needs at minimum Read & Execute permission on the site folder.
# 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

4. Application Pools

Application Pools provide process isolation between websites. Each pool runs as a separate w3wp.exe process.

SettingOptionsRecommendation
.NET CLR Versionv2.0 / v4.0 / No Managed Codev4.0 for ASP.NET 4.x; "No Managed Code" for PHP, static sites, ASP.NET Core
Pipeline ModeIntegrated / ClassicIntegrated (default, better performance). Classic only for legacy ASP apps.
IdentityNetworkService, LocalSystem, LocalService, ApplicationPoolIdentity, CustomApplicationPoolIdentity (default, least privilege). Use custom service account for specific permission needs.
Idle Time-outDefault: 20 minutesSet 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 ProcessesDefault: 1Web 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}}

5. Default Documents and Error Pages

# 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>

6. HTTPS — SSL/TLS Setup

Self-Signed Certificate (Development / Internal)

# 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 PFX Certificate (Production)

# 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 to Site

# 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)

Let's Encrypt with win-acme (Free Public Certificates)

# 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/

7. PHP on IIS

# 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

8. URL Rewrite Module

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>

9. web.config Essentials

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>

10. Application Request Routing (ARR) — Reverse Proxy

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>

11. IIS Logs and Troubleshooting

IIS Log Files

# 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

Common IIS Error Codes and Solutions

ErrorCauseFix
403.14 ForbiddenDirectory listing disabled, no default documentAdd default document, or enable directory browsing
403.18 ForbiddenCannot execute CGI in this application poolCheck app pool pipeline mode — switch to Classic for old CGI apps
404 Not FoundFile missing, URL routing issueVerify file exists; check URL Rewrite rules; check handler mappings
500.19 Internal Server Errorweb.config syntax error or duplicate keysValidate web.config XML; check IIS feature is installed for the config element used
500.21.NET module not loaded or handler not foundEnsure ASP.NET is installed; run aspnet_regiis -i
503 Service UnavailableApp pool stopped or disabledStart 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"

Lesson 4 Complete

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.

Next: DNS & DHCP →

📌 Study Checklist