How to Configure SSL/TLS in IIS on Windows Server 2025
Serving traffic over HTTPS is no longer optional — modern browsers mark HTTP sites as “Not Secure,” search engines penalize them in rankings, and many web application features (service workers, geolocation, payment APIs) are gated behind a secure context. Windows Server 2025 with IIS 10.0 supports TLS 1.2 and TLS 1.3 out of the box, and configuring a site for HTTPS involves three distinct phases: obtaining and importing a certificate, binding that certificate to an IIS site, and hardening the server-level TLS configuration to disable weak protocols and cipher suites. This guide covers all three phases using a combination of PowerShell, the netsh tool, and registry edits.
Prerequisites
- Windows Server 2025 with IIS 10.0 installed and at least one website created
- A certificate — either a self-signed cert for development or a CA-signed .pfx for production
- PowerShell running as Administrator
- Firewall rule permitting inbound TCP 443
- The
WebAdministrationPowerShell module (Import-Module WebAdministration)
Step 1: Obtain or Import a Certificate
For development and internal testing, generate a self-signed certificate directly from PowerShell:
# Generate a self-signed certificate trusted for 'example.local'
$cert = New-SelfSignedCertificate `
-DnsName "example.local", "www.example.local" `
-CertStoreLocation "Cert:LocalMachineMy" `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddDays(365) `
-FriendlyName "example.local TLS Certificate"
Write-Host "Certificate thumbprint: $($cert.Thumbprint)"
For production, you will receive a .pfx file from a Certificate Authority (Let’s Encrypt via Certbot/win-acme, DigiCert, Sectigo, etc.). Import it into the Local Machine store:
# Import a CA-signed PFX certificate
$pfxPath = "C:certsexample.com.pfx"
$pfxPassword = ConvertTo-SecureString -String "YourPfxPassword" -AsPlainText -Force
$imported = Import-PfxCertificate `
-FilePath $pfxPath `
-CertStoreLocation "Cert:LocalMachineMy" `
-Password $pfxPassword `
-Exportable
Write-Host "Imported thumbprint: $($imported.Thumbprint)"
Verify the certificate is present in the store before proceeding:
Get-ChildItem Cert:LocalMachineMy | Select-Object Thumbprint, Subject, NotAfter
Step 2: Bind the Certificate to the IIS Site
There are two complementary methods: using the WebAdministration module and using netsh. The WebAdministration approach is preferred for automation:
Import-Module WebAdministration
$siteName = "MySite"
$thumbprint = $cert.Thumbprint # Replace with $imported.Thumbprint for production
# Add an HTTPS binding with SNI (SslFlags 1 enables SNI, allowing multiple certs per IP)
New-WebBinding -Name $siteName `
-Protocol "https" `
-Port 443 `
-IPAddress "*" `
-HostHeader "example.local" `
-SslFlags 1
# Associate the certificate with this binding
$binding = Get-WebBinding -Name $siteName -Protocol "https"
$binding.AddSslCertificate($thumbprint, "My")
# Verify
Get-WebBinding -Name $siteName | Format-Table Protocol, bindingInformation
Alternatively, bind the certificate using netsh (useful for non-SNI scenarios or scripting without the WebAdministration module):
# netsh requires the application GUID (any GUID will do) and the thumbprint without spaces
$appId = "{$(New-Guid)}"
$thumbHex = $thumbprint -replace ' ', ''
netsh http add sslcert hostnameport="example.local:443" `
certhash=$thumbHex appid=$appId certstorename=MY sslctlstorename=MY
Step 3: Require SSL on the Site
To force all traffic to HTTPS and reject plain HTTP connections at the IIS level, enable the Require SSL setting. This causes IIS to return HTTP 403.4 for any HTTP request, which can be combined with a URL Rewrite redirect rule for a better user experience:
# Enable requireSSL for the site
Set-WebConfigurationProperty `
-Filter "system.webServer/security/access" `
-PSPath "IIS:Sites$siteName" `
-Name "sslFlags" `
-Value "Ssl"
# Optionally also require client certificates (mutual TLS)
# Replace 'Ssl' with 'Ssl,SslNegotiateCert' or 'Ssl,SslRequireCert'
Step 4: Disable Weak TLS Protocols via the Registry
Windows Server 2025 ships with TLS 1.0 and TLS 1.1 disabled by default in the Schannel provider, but it is good practice to verify and explicitly enforce this. The Schannel registry keys control which protocols the OS negotiates:
# Define a helper function to set Schannel protocol state
function Set-SchannelProtocol {
param(
[string]$Protocol,
[string]$Role, # 'Server' or 'Client'
[bool] $Enabled
)
$regPath = "HKLM:SYSTEMCurrentControlSetControlSecurityProvidersSCHANNELProtocols$Protocol$Role"
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
Set-ItemProperty -Path $regPath -Name 'Enabled' -Value ([int]$Enabled) -Type DWord
Set-ItemProperty -Path $regPath -Name 'DisabledByDefault' -Value ([int](!$Enabled)) -Type DWord
}
# Disable TLS 1.0 and TLS 1.1 for both server and client roles
foreach ($proto in @('TLS 1.0', 'TLS 1.1')) {
Set-SchannelProtocol -Protocol $proto -Role 'Server' -Enabled $false
Set-SchannelProtocol -Protocol $proto -Role 'Client' -Enabled $false
}
# Ensure TLS 1.2 and TLS 1.3 are explicitly enabled
foreach ($proto in @('TLS 1.2', 'TLS 1.3')) {
Set-SchannelProtocol -Protocol $proto -Role 'Server' -Enabled $true
Set-SchannelProtocol -Protocol $proto -Role 'Client' -Enabled $true
}
# Disable SSL 2.0 and SSL 3.0 if not already
foreach ($proto in @('SSL 2.0', 'SSL 3.0')) {
Set-SchannelProtocol -Protocol $proto -Role 'Server' -Enabled $false
}
Write-Host "Protocol changes applied. A reboot is required to take effect."
Note: These registry changes require a system reboot to take effect across all Schannel-dependent services, including IIS.
Step 5: Order Cipher Suites for Forward Secrecy
Beyond protocol versions, the order of cipher suites determines which encryption algorithm is negotiated. Prefer cipher suites that provide forward secrecy (ECDHE key exchange) and authenticated encryption (GCM/ChaCha20). The easiest way to manage cipher suites on Windows Server 2025 is with IIS Crypto (from Nartac Software) via its CLI, or directly via Group Policy / registry:
# View current cipher suite order
(Get-TlsCipherSuite | Select-Object -First 10).Name
# Prioritize forward-secret cipher suites using the TLS PowerShell module
# Disable weak suites
$weakSuites = @(
'TLS_RSA_WITH_AES_128_CBC_SHA',
'TLS_RSA_WITH_AES_256_CBC_SHA',
'TLS_RSA_WITH_AES_128_CBC_SHA256',
'TLS_RSA_WITH_AES_256_CBC_SHA256',
'TLS_RSA_WITH_3DES_EDE_CBC_SHA'
)
foreach ($suite in $weakSuites) {
Disable-TlsCipherSuite -Name $suite -ErrorAction SilentlyContinue
}
# Confirm preferred suites are available
Get-TlsCipherSuite | Where-Object { $_.Name -like '*ECDHE*GCM*' } | Select-Object Name
Step 6: Add the HSTS Header
HTTP Strict Transport Security (HSTS) instructs browsers to always use HTTPS for your domain, even if a user types http:// in the address bar. Configure it as a custom response header in IIS:
# Add HSTS header to the site (max-age of 1 year, include subdomains, preload-ready)
Add-WebConfigurationProperty `
-Filter "system.webServer/httpProtocol/customHeaders" `
-PSPath "IIS:Sites$siteName" `
-Name "." `
-Value @{
name = 'Strict-Transport-Security'
value = 'max-age=31536000; includeSubDomains; preload'
}
# Verify the header was added
Get-WebConfiguration "system.webServer/httpProtocol/customHeaders" `
-PSPath "IIS:Sites$siteName" | Select-Object -ExpandProperty Collection
Start with a short max-age (e.g., max-age=300) and increase to the full year only after you are certain all traffic is HTTPS and all subdomains are covered.
Step 7: Verify with an SSL Test
For public-facing sites, submit your domain to SSL Labs’ SSL Server Test to receive a detailed grade. The test checks protocol versions, cipher suite ordering, certificate chain validity, HSTS, and more. An A or A+ rating indicates a well-hardened TLS configuration.
For internal or development servers, use PowerShell to inspect the negotiated TLS handshake:
# Test TLS negotiation from the server itself
$tcpClient = New-Object System.Net.Sockets.TcpClient("example.local", 443)
$sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false, {$true})
$sslStream.AuthenticateAsClient("example.local")
Write-Host "Protocol : $($sslStream.SslProtocol)"
Write-Host "Cipher : $($sslStream.CipherAlgorithm)"
Write-Host "Cipher Bits: $($sslStream.CipherStrength)"
Write-Host "Hash : $($sslStream.HashAlgorithm)"
$sslStream.Close()
$tcpClient.Close()
Conclusion
You have now fully secured an IIS 10.0 website on Windows Server 2025 with TLS: a certificate is imported and bound to the site, plain HTTP is rejected at the server level, outdated protocols (TLS 1.0, 1.1, SSL 2.0/3.0) are disabled via Schannel registry keys, cipher suites are ordered to prefer forward-secret algorithms, and HSTS is in place to instruct browsers to always connect securely. Ongoing certificate lifecycle management — automated renewal via win-acme or Windows ACME Simple for Let’s Encrypt, or scheduled tasks for commercial certificates — is the next step to ensure your HTTPS configuration never lapses. Pair this configuration with IIS’s built-in URL Rewrite module to redirect any remaining HTTP traffic to HTTPS for a seamless user experience.