IIS Hardening in Practice: Windows Web Server Defense Configuration Guide
Request filtering, URL authorization, NTFS ACLs, HTTP security headers, TLS version limits. Full IIS hardening steps with runnable PowerShell and verification. For operators locking down Windows web servers.
IIS hardening is the baseline for Windows servers hosting sites against web attacks. For ops running IIS on Windows Server for ASP.NET, PHP, etc.: complete flow from request filtering, URL authorization, NTFS ACLs, to TLS version restriction. All commands run directly in PowerShell.
Applicable Scenarios
Prioritize IIS hardening when:
- Sites on Windows servers have suffered directory traversal, double-extension bypass, or file-upload attacks
- Scanners find sensitive files (.git, .bak, .config) downloadable
- Need MLPS 2.0, PCI DSS web-server security baselines
- IIS logs show frequent malformed HTTP requests or abnormal HTTP verbs
Prerequisites
- OS: Windows Server 2016 / 2019 / 2022
- IIS 8.5+ (bundled on Windows Server 2012 R2+)
- Local Administrator; run commands in an elevated PowerShell window
How It Works
IIS processes each HTTP request along a fixed pipeline: Request Filtering → URL Authorization → Handler Mapping → Authentication. Hardening tightens default policy at each stage: request filtering intercepts malformed and dangerous requests first; URL authorization controls who can hit which path; NTFS ACLs are the filesystem last line. Config stacks at server level (applicationHost.config) and site level (web.config); site-level wins.
Procedure
Step 1: Confirm IIS Version
`# Check IIS version (10.0 corresponds to Windows Server 2016+)
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\InetStp" | Select-Object MajorVersion, MinorVersion`
Step 2: Configure Request Filtering (core)
`# Deny double-extension requests (e.g. shell.asp.jpg); block sensitive suffixes such as .git and .bak
Import-Module WebAdministration
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/fileExtensions" -Name collection -Value @{fileExtension=".git"; allowed="False"} -PSPath "IIS:\Sites\Default Web Site"
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/fileExtensions" -Name collection -Value @{fileExtension=".bak"; allowed="False"}
# Limit URL length to 4096, query string to 2048, single-file upload to 30MB
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/requestLimits" -Name maxUrl -Value 4096
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/requestLimits" -Name maxQueryString -Value 2048
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/requestLimits" -Name maxAllowedContentLength -Value 31457280
# Allow only common verbs; deny TRACE, DELETE, PUT
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/verbs" -Name collection -Value @{verb="TRACE"; allowed="False"}
Set-WebConfigurationProperty -Filter "system.webServer/security/requestFiltering/verbs" -Name collection -Value @{verb="DELETE"; allowed="False"}`
Step 3: Tighten URL Authorization and NTFS ACLs
`# Remove Everyone Full Control from the site root; keep only Administrators/application-pool identity Read
icacls "C:\inetpub\wwwroot" /remove "Everyone" /T
# Block web access to sensitive directories (add to the site web.config)
# <system.webServer><security><authorization><remove users="*" roles="" verbs="" /><add accessType="Deny" users="*" /></authorization></security>`
Complete URL authorization fragment in web.config:
`<configuration>
<location path="App_Data">
<system.webServer>
<security>
<authorization>
<remove users="*" roles="" verbs="" />
<add accessType="Deny" users="*" />
</authorization>
</security>
</system.webServer>
</location>
</configuration>`
Step 4: Add HTTP Security Response Headers
`# Add under the system.webServer node in the site web.config
# <httpProtocol><customHeaders>
# <add name="X-Content-Type-Options" value="nosniff" />
# <add name="X-Frame-Options" value="SAMEORIGIN" />
# <add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
# <add name="Strict-Transport-Security" value="max-age=31536000" />
# </customHeaders></httpProtocol>`
Step 5: Force HTTPS and Disable Legacy TLS
`# Disable TLS 1.0 / 1.1; enable TLS 1.2 / 1.3 (takes effect after restart)
New-Item "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server" -Force
New-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server" -Name Enabled -Value 0 -PropertyType DWord -Force
New-Item "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server" -Force
New-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server" -Name Enabled -Value 0 -PropertyType DWord -Force
Restart-Service W3SVC`
Step 6: Enable Detailed W3C Logging
`# Ensure logs include User-Agent, Referer, and X-Forwarded-For (reverse-proxy scenarios)
Set-WebConfigurationProperty -Filter "system.applicationHost/sites/siteDefaults/logFile" -Name logFormat -Value "W3C"
# In IIS Manager → Site → Logging → Select Fields, check User-Agent / Referer`
Configuration Verification
`# Check security response headers
curl.exe -I https://your-domain.com
# Verify sensitive suffixes are blocked (should return 404.7)
curl.exe -s -o /dev/null -w "%{http_code}" https://your-domain.com/shell.asp.jpg
# Check whether request-filtering configuration is in effect
Get-WebConfiguration -Filter "system.webServer/security/requestFiltering" | Select-Object -ExpandProperty Property`
FAQ
Q1: After enabling request filtering, legitimate features return 404.7 or 405?
Identify the block reason: 404.7 is denied file extension; 405 is denied HTTP verb. For legitimate cases (e.g. WebDAV upload), open only that directory in requestFiltering—never relax globally. If the site truly needs PUT/DELETE, at least keep TRACE disabled and add source-IP restrictions on those verbs.
Q2: How to harden old IIS 6 (Windows Server 2003) sites?
IIS 6 is out of security support and lacks advanced request filtering. Migrate to IIS 10 (Windows Server 2019/2022) ASAP. Interim minimum: disable WebDAV, remove unused ISAPI filters, enable URLScan 3.0 equivalent config, install patches promptly.
Q3: After setting the HSTS header, all HTTP access is blocked. How to roll back?
During HSTS max-age, browsers force HTTPS. Rollback is two steps: remove the HSTS header and serve max-age=0 (browsers clear the policy), then restore HTTP redirect logic. In production, gray-release with max-age=3600 first, then raise to 31536000.
Summary
IIS hardening follows defense-in-depth: request filtering blocks malformed requests, URL authorization controls paths, NTFS ACLs backstop the filesystem, TLS version limits close protocol bugs, detailed logs support forensics. After these six steps, review request-filtering rules and anomalous log entries monthly, and pair with a WAF for layered defense.