Introduction to IIS SSL/TLS Configuration on Windows Server 2019
Internet Information Services (IIS) 10 on Windows Server 2019 supports TLS 1.0 through TLS 1.3, with TLS 1.3 enabled by default when running on Windows Server 2019 version 1903 or later. Configuring SSL/TLS correctly on IIS means obtaining and binding a valid certificate, disabling outdated protocol versions (TLS 1.0, TLS 1.1, SSLv2, SSLv3), configuring strong cipher suites, enabling HTTP Strict Transport Security (HSTS), and setting up automatic certificate renewal. This guide covers all of these steps in detail for a production IIS deployment.
Install IIS with HTTPS Support
# Install IIS with management tools and common modules
Install-WindowsFeature -Name Web-Server, Web-Mgmt-Tools, Web-SSL -IncludeManagementTools
# Confirm installed features
Get-WindowsFeature -Name Web-* | Where-Object InstallState -eq Installed |
Select-Object Name, DisplayName | Format-Table
Obtain a TLS Certificate
For internet-facing sites, use a certificate from a public CA such as Let’s Encrypt, DigiCert, or Sectigo. For internal sites, use a certificate from your internal Certificate Authority (ADCS). The following example shows both approaches.
# Method 1: Request certificate from internal ADCS
# Create a certificate request using certreq
$inf = @"
[Version]
Signature="$Windows NT$"
[NewRequest]
Subject = "CN=www.corp.example.com,O=Corp,C=GB"
KeySpec = 1
KeyLength = 2048
Exportable = TRUE
MachineKeySet = TRUE
SMIME = False
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
[EnhancedKeyUsageExtension]
OID=1.3.6.1.5.5.7.3.1
[Extensions]
2.5.29.17 = "{text}"
_continue_ = "dns=www.corp.example.com&"
_continue_ = "dns=corp.example.com"
"@
$inf | Set-Content -Path "C:cert-request.inf"
certreq -new "C:cert-request.inf" "C:cert-request.csr"
# Submit to CA and retrieve
certreq -submit -attrib "CertificateTemplate:WebServer" "C:cert-request.csr" "C:cert.cer"
certreq -accept "C:cert.cer"
# Method 2: Use PowerShell New-SelfSignedCertificate for lab/internal use
$cert = New-SelfSignedCertificate `
-DnsName "www.corp.example.com","corp.example.com" `
-CertStoreLocation "Cert:LocalMachineMy" `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-NotAfter (Get-Date).AddYears(2) `
-FriendlyName "IIS TLS Certificate"
Write-Host "Certificate thumbprint: $($cert.Thumbprint)"
Bind the Certificate to an IIS Website
# Get the certificate thumbprint
$cert = Get-ChildItem -Path "Cert:LocalMachineMy" |
Where-Object Subject -match "www.corp.example.com" |
Select-Object -First 1
# Create the HTTPS binding on port 443 for the Default Web Site
New-WebBinding -Name "Default Web Site" `
-Protocol "https" `
-Port 443 `
-IPAddress "*" `
-HostHeader "www.corp.example.com" `
-SslFlags 1 # 1 = SNI enabled
# Bind the certificate to the HTTPS binding
$binding = Get-WebBinding -Name "Default Web Site" -Protocol "https" -Port 443
$binding.AddSslCertificate($cert.Thumbprint, "My")
# Verify the binding
Get-WebBinding -Name "Default Web Site" | Format-Table Protocol, BindingInformation
The SslFlags = 1 enables Server Name Indication (SNI), which allows multiple HTTPS sites with different certificates to share the same IP address and port. Without SNI, each HTTPS site requires a dedicated IP address.
Disable Insecure Protocol Versions
Windows Server 2019 still ships with TLS 1.0 and TLS 1.1 enabled for backward compatibility. Disable these and SSLv3/SSLv2 via the registry. Changes require a server reboot to take effect:
# Disable SSL 2.0
$ssl2Path = "HKLM:SYSTEMCurrentControlSetControlSecurityProvidersSCHANNELProtocolsSSL 2.0Server"
New-Item -Path $ssl2Path -Force
Set-ItemProperty -Path $ssl2Path -Name "Enabled" -Value 0 -Type DWord
Set-ItemProperty -Path $ssl2Path -Name "DisabledByDefault" -Value 1 -Type DWord
# Disable SSL 3.0
$ssl3Path = "HKLM:SYSTEMCurrentControlSetControlSecurityProvidersSCHANNELProtocolsSSL 3.0Server"
New-Item -Path $ssl3Path -Force
Set-ItemProperty -Path $ssl3Path -Name "Enabled" -Value 0 -Type DWord
Set-ItemProperty -Path $ssl3Path -Name "DisabledByDefault" -Value 1 -Type DWord
# Disable TLS 1.0
$tls10Path = "HKLM:SYSTEMCurrentControlSetControlSecurityProvidersSCHANNELProtocolsTLS 1.0Server"
New-Item -Path $tls10Path -Force
Set-ItemProperty -Path $tls10Path -Name "Enabled" -Value 0 -Type DWord
Set-ItemProperty -Path $tls10Path -Name "DisabledByDefault" -Value 1 -Type DWord
# Disable TLS 1.1
$tls11Path = "HKLM:SYSTEMCurrentControlSetControlSecurityProvidersSCHANNELProtocolsTLS 1.1Server"
New-Item -Path $tls11Path -Force
Set-ItemProperty -Path $tls11Path -Name "Enabled" -Value 0 -Type DWord
Set-ItemProperty -Path $tls11Path -Name "DisabledByDefault" -Value 1 -Type DWord
# Ensure TLS 1.2 is explicitly enabled
$tls12Path = "HKLM:SYSTEMCurrentControlSetControlSecurityProvidersSCHANNELProtocolsTLS 1.2Server"
New-Item -Path $tls12Path -Force
Set-ItemProperty -Path $tls12Path -Name "Enabled" -Value 1 -Type DWord
Set-ItemProperty -Path $tls12Path -Name "DisabledByDefault" -Value 0 -Type DWord
Configure Strong Cipher Suites
Use Group Policy or PowerShell to enforce a strong cipher suite order, prioritising ECDHE and AES-GCM over older algorithms:
# Set cipher suite order via Group Policy registry path
$cipherSuites = @(
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
)
$cipherString = $cipherSuites -join ","
# Apply via Group Policy registry
$policyPath = "HKLM:SOFTWAREPoliciesMicrosoftCryptographyConfigurationSSL0010002"
New-Item -Path $policyPath -Force
Set-ItemProperty -Path $policyPath -Name "Functions" -Value $cipherString -Type String
Enable HTTP Strict Transport Security (HSTS)
HSTS instructs browsers to always use HTTPS for your domain, even if a user types http://. Configure it in IIS via web.config or PowerShell:
# Enable HSTS via IIS PowerShell module (requires IIS 10 on Server 2019 1709+)
Set-WebConfigurationProperty `
-PSPath "IIS:SitesDefault Web Site" `
-Filter "system.webServer/httpProtocol/customHeaders" `
-Name "." `
-Value @{
name = "Strict-Transport-Security"
value = "max-age=31536000; includeSubDomains; preload"
}
# Or add directly to web.config:
$webConfigPath = "C:inetpubwwwrootweb.config"
[xml]$webConfig = Get-Content $webConfigPath
$httpProtocol = $webConfig.SelectSingleNode("//system.webServer/httpProtocol")
if (-not $httpProtocol) {
$httpProtocol = $webConfig.CreateElement("httpProtocol")
$webConfig.SelectSingleNode("//system.webServer").AppendChild($httpProtocol)
}
# (further manipulation for customHeaders node as needed)
Redirect HTTP to HTTPS
# Install URL Rewrite module first (if not already installed)
# Then add HTTP to HTTPS redirect rule in web.config
$httpsRedirectRule = @"
"@
# Add this inside in your site's web.config
Verify TLS Configuration
# Test TLS handshake using PowerShell
$hostname = "www.corp.example.com"
$port = 443
$tcp = [System.Net.Sockets.TcpClient]::new($hostname, $port)
$ssl = [System.Net.Security.SslStream]::new($tcp.GetStream())
$ssl.AuthenticateAsClient($hostname)
Write-Host "TLS Version: $($ssl.SslProtocol)"
Write-Host "Cipher: $($ssl.CipherAlgorithm)"
Write-Host "Cert Subject: $($ssl.RemoteCertificate.Subject)"
Write-Host "Cert Valid To: $($ssl.RemoteCertificate.GetExpirationDateString())"
$ssl.Dispose(); $tcp.Dispose()
Summary
A secure IIS TLS configuration on Windows Server 2019 requires binding a valid certificate with SNI, disabling SSLv2, SSLv3, TLS 1.0, and TLS 1.1 via registry, enforcing strong ECDHE cipher suites, enabling HSTS to prevent protocol downgrade, and redirecting all HTTP traffic to HTTPS. These steps together achieve an A+ rating on SSL Labs and meet modern security compliance requirements including PCI DSS and NIST guidelines.