Windows Server

IIS: Web Server on Windows Server

Install and configure IIS (Internet Information Services) on Windows Server. Hosting websites, ASP.NET, PHP and SSL management.

IIS (Internet Information Services) is the integrated web server in Windows Server. Ideal for ASP.NET applications, WordPress on Windows and static sites.


IIS Installation

# Install IIS with common features
Install-WindowsFeature -Name Web-Server -IncludeManagementTools

# Or with additional features (ASP.NET 4.8, URL rewrite modules)
Install-WindowsFeature -Name `
  Web-Server, `
  Web-Common-Http, `
  Web-Static-Content, `
  Web-Default-Doc, `
  Web-Dir-Browsing, `
  Web-Http-Errors, `
  Web-Http-Logging, `
  Web-Asp-Net45, `
  Web-Net-Ext45, `
  Web-ISAPI-Ext, `
  Web-ISAPI-Filter, `
  Web-Mgmt-Console, `
  Web-Mgmt-Tools `
  -IncludeManagementTools

Verify Installation

# Check IIS is running
Get-Service W3SVC
# Open browser on http://localhost: you should see IIS default page

Manage via IIS Manager

Open IIS Manager: Win+Rinetmgr

Main structure:

  • Sites: all configured websites
  • Application Pools: isolated process pools for each app
  • Default Web Site: default site on port 80

Add a Website

PowerShell

# Create site directory
New-Item -ItemType Directory -Path "C:\inetpub\mysite"

# Create dedicated Application Pool
New-WebAppPool -Name "MySitePool"

# Create site
New-Website -Name "MySite" `
  -PhysicalPath "C:\inetpub\mysite" `
  -ApplicationPool "MySitePool" `
  -Port 80 `
  -HostHeader "mysite.com"

Folder Permissions

# IIS account (IIS_IUSRS) needs read access
icacls "C:\inetpub\mysite" /grant "IIS_IUSRS:(OI)(CI)R" /T
# For write (e.g. WordPress):
icacls "C:\inetpub\mysite" /grant "IIS_IUSRS:(OI)(CI)M" /T

SSL with Let's Encrypt (win-acme)

# Download win-acme (Let's Encrypt client for IIS)
# https://github.com/win-acme/win-acme/releases
# Extract to C:\win-acme

cd C:\win-acme
.\wacs.exe
# Follow wizard: choose IIS site → automatic renewal via Task Scheduler

Automatic Renewal

win-acme automatically creates a Task Scheduler task to renew certificate before expiration. No manual action needed.


PHP on IIS (with PHP Manager)

# 1. Download PHP for Windows (Non-Thread Safe) from https://windows.php.net/download
# 2. Extract to C:\PHP

# 3. Install PHP Manager module for IIS Manager (GUI)
# https://www.iis.net/downloads/community/2018/05/php-manager-150-for-iis-10

# 4. Register PHP via PowerShell
$phpPath = "C:\PHP\php-cgi.exe"
& "$env:SystemRoot\system32\inetsrv\appcmd.exe" set config `
  /section:system.webServer/fastCGI `
  /+[fullPath="$phpPath"]

& "$env:SystemRoot\system32\inetsrv\appcmd.exe" set config `
  /section:system.webServer/handlers `
  /+[name="PHP_via_FastCGI",path="*.php",verb="*",modules="FastCgiModule",scriptProcessor="$phpPath",resourceType="Either"]

Verify with info.php file:

'<?php phpinfo(); ?>' | Out-File "C:\inetpub\mysite\info.php" -Encoding utf8
# Visit http://mysite.com/info.php

URL Rewrite (necessary for WordPress, Laravel, etc.)

# Download and install URL Rewrite from:
# https://www.iis.net/downloads/microsoft/url-rewrite
# Then restart IIS

# Verify installation
Get-WebGlobalModule -Name "RewriteModule"

For WordPress, create web.config in site root:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="WordPress" patternSyntax="Wildcard">
          <match url="*" />
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="index.php" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Useful PowerShell Commands

# List all sites
Get-Website

# Start/stop site
Start-Website -Name "MySite"
Stop-Website -Name "MySite"

# Restart IIS completely
iisreset

# Only restart worker processes (faster)
iisreset /restart

# List Application Pools
Get-WebConfiguration system.applicationHost/applicationPools/add | Select name, state

# Recycle pool (useful after deploy)
Restart-WebAppPool -Name "MySitePool"

# View access logs
Get-Content "C:\inetpub\logs\LogFiles\W3SVC1\u_ex*.log" -Tail 50

Performance Optimization

# Increase pool idle timeout (default 20 min: causes cold start)
Set-ItemProperty "IIS:\AppPools\MySitePool" -Name processModel.idleTimeout -Value "00:00:00"

# Enable gzip compression
& "$env:SystemRoot\system32\inetsrv\appcmd.exe" set config `
  -section:httpCompression -[name='gzip'].doStaticCompression:true

# Enable static file caching
Set-WebConfigurationProperty -filter "system.webServer/staticContent" `
  -name "." -value @{clientCache=@{cacheControlMode="UseMaxAge";cacheControlMaxAge="7.00:00:00"}}

Troubleshooting

403 Forbidden Error

# Check permissions
icacls "C:\inetpub\mysite"
# Add IIS_IUSRS if missing
icacls "C:\inetpub\mysite" /grant "IIS_IUSRS:(OI)(CI)R"

500 Internal Server Error

# Enable detailed error messages (dev only!)
Set-WebConfigurationProperty -filter "system.web/customErrors" `
  -name "mode" -value "Off" -PSPath "IIS:\Sites\MySite"

# Or check Windows Event Log for ASP.NET errors
Get-EventLog -LogName Application -Source "ASP.NET*" -Newest 20

Port 80 Already in Use

# Find what uses port 80
netstat -ano | findstr :80
# Then find PID in Task Manager

On this page