How to Set Up IIS 8.5 Reverse Proxy on Windows Server 2012 R2

IIS 8.5 on Windows Server 2012 R2, combined with the Application Request Routing (ARR) extension and URL Rewrite module, creates a powerful reverse proxy platform that can load-balance web applications, forward requests to backend servers, terminate SSL, and provide WAF-like URL filtering. A reverse proxy sits in front of your application servers, shielding them from direct internet exposure, centralizing SSL termination, and enabling horizontal scaling. This guide covers deploying IIS 8.5 as a reverse proxy with ARR for load balancing and URL Rewrite for path-based routing.

Prerequisites

– Windows Server 2012 R2 with IIS 8.5 role installed
– Application Request Routing (ARR) 3.0 extension (free download from Microsoft)
– URL Rewrite 2.0 extension (free download from Microsoft)
– SSL certificate for the public-facing HTTPS endpoint
– Backend servers reachable from the IIS ARR server on their service ports
– Two or more backend web servers for load balancing
– Network connectivity and firewall rules between the ARR server and backend pool members

Step 1: Install IIS 8.5 with Required Role Services

# Install IIS with all required role services for ARR
Install-WindowsFeature -Name Web-Server `
    -IncludeManagementTools `
    -IncludeAllSubFeature

# Specifically ensure these are installed:
Install-WindowsFeature -Name @(
    "Web-Default-Doc",
    "Web-Dir-Browsing",
    "Web-Http-Errors",
    "Web-Static-Content",
    "Web-Http-Redirect",
    "Web-Http-Logging",
    "Web-Log-Libraries",
    "Web-Request-Monitor",
    "Web-Http-Tracing",
    "Web-Stat-Compression",
    "Web-Dyn-Compression",
    "Web-Filtering",
    "Web-Basic-Auth",
    "Web-Windows-Auth",
    "Web-Net-Ext45",
    "Web-ASP-Net45",
    "Web-ISAPI-Ext",
    "Web-ISAPI-Filter",
    "Web-Mgmt-Console",
    "Web-Scripting-Tools",
    "Web-Mgmt-Service"
)

# Verify IIS is running
Get-Service W3SVC | Select-Object Status, StartType
Import-Module WebAdministration
Get-WebSite | Select-Object Name, State, PhysicalPath | Format-Table -AutoSize

Step 2: Install Application Request Routing and URL Rewrite

# Download and install ARR 3.0 and URL Rewrite 2.0 from Microsoft
# ARR includes URL Rewrite as a dependency, but both can be installed separately

# Install via Web Platform Installer (WebPI) silently:
# WebPICMD.exe /Install /Products:ARR /AcceptEula

# Or install MSI packages:
# URL Rewrite: rewrite_amd64_en-US.msi
# ARR: requestRouter_amd64.msi

$installers = @(
    @{
        Name = "URL Rewrite 2.0"
        Url  = "https://download.microsoft.com/download/rewrite_amd64_en-US.msi"
        File = "C:DownloadsURLRewrite.msi"
    },
    @{
        Name = "Application Request Routing 3.0"
        Url  = "https://download.microsoft.com/download/ARR30.msi"
        File = "C:DownloadsARR30.msi"
    }
)

foreach ($installer in $installers) {
    # Download and install
    Start-Process msiexec.exe -ArgumentList "/i `"$($installer.File)`" /qn /norestart" `
        -Wait -PassThru
    Write-Host "$($installer.Name) installed"
}

# Verify ARR module is loaded in IIS
Get-WebConfiguration "system.webServer/globalModules" | 
    Where-Object { $_.name -like "*ARR*" -or $_.name -like "*RewriteModule*" } |
    Select-Object name | Format-Table -AutoSize

Step 3: Enable ARR Proxy

Import-Module WebAdministration

# Enable Application Request Routing proxy feature
# This must be done at the server level, not per-site
Set-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "system.webServer/proxy" `
    -Value @{enabled = $true}

# Enable reverse proxy mode
Add-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "system.webServer/proxy" `
    -AtIndex 0 -Value @{
        enabled = $true
        reverseRewriteHostInResponseHeaders = $true
        preserveHostHeader = $true
    }

# Configure proxy timeouts
Set-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "system.webServer/proxy" `
    -Value @{
        timeout = "00:01:30"
        connectTimeout = "00:00:15"
    }

# Verify proxy is enabled
Get-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "system.webServer/proxy" | Format-List *

Step 4: Create a Server Farm for Load Balancing

Import-Module WebAdministration

$farmName    = "BackendWebFarm"
$backendPorts= 80  # Internal backend port

# Create the upstream server farm
Add-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "webFarms" `
    -Value @{name = $farmName; enabled = $true}

# Add backend servers to the farm
$backends = @(
    @{Name="WebServer01"; Address="10.10.10.21"; Port=80; Weight=100},
    @{Name="WebServer02"; Address="10.10.10.22"; Port=80; Weight=100},
    @{Name="WebServer03"; Address="10.10.10.23"; Port=80; Weight=100}
)

foreach ($backend in $backends) {
    Add-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
        -Filter "webFarms/webFarm[@name='$farmName']/server" `
        -Value @{
            address = $backend.Address
            enabled = $true
            httpPort = $backend.Port
            weight   = $backend.Weight
        }
    Write-Host "Added backend: $($backend.Name) ($($backend.Address))" -ForegroundColor Green
}

# Configure load balancing algorithm
Set-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "webFarms/webFarm[@name='$farmName']/applicationRequestRouting" `
    -Value @{
        loadBalancingAlgorithm = "WeightedRoundRobin"
    }

# Configure health check
Set-WebConfiguration -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "webFarms/webFarm[@name='$farmName']/applicationRequestRouting/healthCheck" `
    -Value @{
        enabled = $true
        url     = "http://webserver01/health"
        interval= "00:00:30"
        responseMatch = "Healthy"
        failureCount  = 3
        timeout = "00:00:10"
    }

Write-Host "Server farm '$farmName' created with $($backends.Count) backends"

Step 5: Configure URL Rewrite Rules for Reverse Proxy

Import-Module WebAdministration

# Create URL rewrite rule to forward all traffic to the server farm
# This rule is applied at the Default Web Site level

# Rule 1: Forward all requests to the backend farm
Add-WebConfigurationProperty `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules" `
    -Name "." `
    -Value @{
        name       = "ARR Reverse Proxy"
        enabled    = $true
        patternSyntax = "Regular Expressions"
        stopProcessing = $true
    }

Set-WebConfiguration `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='ARR Reverse Proxy']/match" `
    -Value @{url = "(.*)"; ignoreCase = $true}

Set-WebConfiguration `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='ARR Reverse Proxy']/action" `
    -Value @{type = "Rewrite"; url = "http://$farmName/{R:1}"; appendQueryString = $true}

# Rule 2: Path-based routing — /api/* goes to API servers
Add-WebConfigurationProperty `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules" `
    -Name "." `
    -Value @{name = "Route API to API Farm"; enabled = $true; stopProcessing = $true}

Set-WebConfiguration `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='Route API to API Farm']/match" `
    -Value @{url = "^api/(.*)"; ignoreCase = $true}

Set-WebConfiguration `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='Route API to API Farm']/action" `
    -Value @{type = "Rewrite"; url = "http://APIFarm/{R:1}"; appendQueryString = $true}

Step 6: Configure SSL Termination

Import-Module WebAdministration

# Import SSL certificate
$certPwd = ConvertTo-SecureString "CertP@ss!" -AsPlainText -Force
$cert = Import-PfxCertificate -FilePath "C:Certswebapp_cert.pfx" `
    -CertStoreLocation "Cert:LocalMachineMy" -Password $certPwd

# Add HTTPS binding to the Default Web Site
New-WebBinding -Name "Default Web Site" `
    -Protocol "https" `
    -Port 443 `
    -IPAddress "*" `
    -SslFlags 0

# Bind the SSL certificate to the HTTPS endpoint
$binding = Get-WebBinding -Name "Default Web Site" -Protocol "https"
$binding.AddSslCertificate($cert.Thumbprint, "My")

# Redirect HTTP to HTTPS
Add-WebConfigurationProperty `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules" `
    -Name "." `
    -Value @{name = "HTTPS Redirect"; enabled = $true; stopProcessing = $true}

Set-WebConfiguration `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='HTTPS Redirect']/match" `
    -Value @{url = "(.*)"}

Set-WebConfiguration `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='HTTPS Redirect']/conditions" `
    -Value @{logicalGrouping = "MatchAny"}

Add-WebConfigurationProperty `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='HTTPS Redirect']/conditions" `
    -Name "." `
    -Value @{input = "{HTTPS}"; matchType = "Pattern"; pattern = "^OFF$"}

Set-WebConfiguration `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
    -Filter "system.webServer/rewrite/rules/rule[@name='HTTPS Redirect']/action" `
    -Value @{type = "Redirect"; url = "https://{HTTP_HOST}/{R:1}"; redirectType = "Permanent"}

Step 7: Configure HTTP Security Headers

# Add security headers via URL Rewrite outbound rules
# These headers protect clients from common web attacks

$securityHeaders = @(
    @{Name="X-Frame-Options";          Value="SAMEORIGIN"},
    @{Name="X-Content-Type-Options";   Value="nosniff"},
    @{Name="X-XSS-Protection";         Value="1; mode=block"},
    @{Name="Strict-Transport-Security";Value="max-age=31536000; includeSubDomains"},
    @{Name="Content-Security-Policy";  Value="default-src 'self' https:"}
)

foreach ($header in $securityHeaders) {
    Add-WebConfigurationProperty `
        -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" `
        -Filter "system.webServer/httpProtocol/customHeaders" `
        -Name "." `
        -Value @{name=$header.Name; value=$header.Value}
    Write-Host "Added header: $($header.Name)" -ForegroundColor Green
}

# Remove IIS version disclosure header
Remove-WebConfigurationProperty `
    -PSPath "MACHINE/WEBROOT/APPHOST" `
    -Filter "system.webServer/httpProtocol/customHeaders" `
    -Name "." -AtElement @{name="X-Powered-By"}

Verification

Import-Module WebAdministration

Write-Host "=== IIS ARR Reverse Proxy Verification ===" -ForegroundColor Cyan

# IIS service status
Get-Service W3SVC | Select-Object Status, StartType

# Web sites
Get-WebSite | Select-Object Name, State, @{n='Bindings';e={$_.Bindings.Collection.bindingInformation -join ', '}} |
    Format-Table -AutoSize

# ARR proxy status
Get-WebConfiguration "system.webServer/proxy" | Select-Object enabled | Format-List

# Server farms
Get-WebConfiguration "webFarms/webFarm" | Select-Object name, enabled | Format-Table

# URL Rewrite rules
Get-WebConfiguration "system.webServer/rewrite/rules/rule" `
    -PSPath "MACHINE/WEBROOT/APPHOST/Default Web Site" |
    Select-Object name, enabled | Format-Table -AutoSize

# Test reverse proxy
Invoke-WebRequest -Uri "https://localhost" -UseBasicParsing -SkipCertificateCheck |
    Select-Object StatusCode, @{n='Server';e={$_.Headers['Server']}}

Summary

IIS 8.5 with Application Request Routing on Windows Server 2012 R2 creates a feature-rich reverse proxy and load balancer without requiring any third-party software. By installing ARR and URL Rewrite, enabling the proxy feature, creating a server farm with backend servers and health checking, configuring URL Rewrite rules for request forwarding, implementing SSL termination with HTTPS redirect, and adding security headers, you deploy a production-ready reverse proxy that shields backend servers from direct exposure, distributes load across multiple application servers, and provides a centralized point for SSL certificate management, security policy enforcement, and request logging. The weighted round-robin load balancing and health checks ensure traffic is only sent to healthy backends, providing automatic failover within the server farm.