🌐 IIS

IIS SSL/TLS Configuration: HTTPS and Certificate Management

📅 July 21, 20269 min readITVedas

How to bind certificates in IIS, enable SNI, configure HSTS safely, automate renewal with win-acme, and disable weak TLS versions.

INTERMEDIATE
⏱ 9 min read
Prerequisites:
Key Facts
  • SNI (defined in RFC 6066) lets one IP:port combination host multiple HTTPS sites with different certificates by sending the requested hostname unencrypted in the TLS ClientHello
  • HTTPS certificate bindings in IIS are actually stored by http.sys at the OS level — `netsh http show sslcert` shows them independently of IIS Manager
  • Let's Encrypt certificates are valid for only 90 days, which makes renewal automation effectively mandatory rather than optional
  • Once a browser caches an HSTS policy for a host, it will refuse plain HTTP connections to that host for the full max-age with no click-through override

How IIS Certificate Bindings Actually Work

IIS doesn't terminate TLS itself — that job belongs to http.sys, the kernel-mode HTTP listener that ships with Windows. When you "bind a certificate to a site" in IIS Manager, you're really creating an entry in http.sys that maps an IP address (or hostname), port, and optionally a Server Name Indication (SNI) value to a certificate thumbprint sitting in the Windows certificate store. IIS Manager is a friendly front end over this mechanism; the actual state lives outside IIS's own configuration files.

This matters operationally because bindings can go stale independently of the IIS config. If a certificate is renewed and reissued with a new thumbprint but the http.sys binding isn't updated to point at it, the site keeps serving the old (possibly expired) certificate even though the correct one is sitting in the store. You can inspect the raw state at any time with:

netsh http show sslcert

This lists every IP:port and hostname:port binding, the certificate hash (thumbprint) it points to, and the store it's read from. When troubleshooting "wrong certificate served" issues, this command is more trustworthy than the IIS Manager bindings dialog, because it shows exactly what http.sys will present at the TLS handshake.

Binding a Certificate: IIS Manager, PowerShell, and netsh

In IIS Manager, binding a certificate is done from the site's Bindings dialog: select the site, choose Edit Bindings, click Add, set Type to https, choose the IP address and port, and select the certificate from the SSL certificate dropdown. That dropdown only lists certificates that are installed into the Local Computer\Personal store with an exportable or accessible private key — a certificate without a private key, or one sitting only in the user's personal store, will not appear.

The same operation in PowerShell, useful for scripting deployments across multiple servers:

$cert = Get-ChildItem -Path Cert:\LocalMachine\My |
    Where-Object { $_.Subject -like "*example.com*" }

New-WebBinding -Name "Default Web Site" -Protocol https `
    -Port 443 -HostHeader "www.example.com" -SslFlags 1

$binding = Get-WebBinding -Name "Default Web Site" -Protocol https `
    -HostHeader "www.example.com"
$binding.AddSslCertificate($cert.Thumbprint, "My")

The -SslFlags 1 value is what tells IIS to register the binding as SNI-based (hostname-based) rather than the legacy IP-based model — more on this below. Under the hood, both the IIS Manager click-path and the PowerShell WebAdministration cmdlets ultimately drive the same http.sys API that the netsh tool exposes directly:

netsh http add sslcert hostnameport=www.example.com:443 ^
    certhash=THUMBPRINT_HERE ^
    certstorename=MY ^
    appid={00000000-0000-0000-0000-000000000000}

The appid is an arbitrary GUID IIS uses to track which application owns the binding; it doesn't need to correspond to anything meaningful, but it must be present. For the older IP-based (non-SNI) binding style, you'd use ipport= instead of hostnameport=. Knowing this equivalence is useful when a binding gets corrupted and IIS Manager refuses to let you re-add it cleanly — deleting the stale entry with netsh http delete sslcert and re-adding it directly often resolves what the GUI cannot.

SNI: Hosting Multiple HTTPS Sites on One IP

Before SNI, TLS had a structural limitation: the server had to select and present a certificate during the handshake before it could see which hostname the client was actually requesting, because that information (the HTTP Host header) only arrives after encryption is established. With only one certificate available per IP:port pair, hosting multiple HTTPS sites with different certificates required one IP address per site.

Server Name Indication (SNI), defined in RFC 6066, fixes this by having the client include the target hostname — in plaintext — as an extension in the initial TLS ClientHello message, before the handshake completes. This lets the server look at the requested hostname and select the matching certificate from among several bound to the same IP:port, exactly the way the HTTP Host header lets a single IP serve multiple unencrypted sites. This is precisely what the -SslFlags 1 / hostnameport= binding style enables in IIS: it registers the certificate against a hostname rather than an IP address alone.

SNI support is effectively universal in modern browsers, mobile OSes, and HTTP client libraries. The practical exceptions are old platforms: Internet Explorer on Windows XP, Android below 2.3, and certain outdated embedded or API clients that pin to ancient TLS stacks. For virtually all public-facing sites in 2026, this is a non-issue, but it's worth checking client logs before consolidating certificate-per-IP hosting onto SNI if you serve legacy corporate or IoT clients — those may fail the handshake entirely rather than degrade gracefully. IIS 8 and later fully support SNI bindings; there is no reason to keep dedicated IPs per HTTPS site on a current IIS version unless a specific legacy client requires it.

HSTS: What It Does and Why to Roll It Out Carefully

HTTP Strict Transport Security (HSTS) is a response header — Strict-Transport-Security: max-age=31536000; includeSubDomains — that instructs the browser to treat a domain as HTTPS-only for the duration specified in max-age (in seconds). Once a browser has seen and cached this header for a domain, it will automatically rewrite any subsequent http:// request to that domain to https:// before it ever hits the network, and it will hard-fail (no "proceed anyway" option) on any certificate error, rather than showing the usual click-through warning.

IIS 10 on Windows Server 2019 and later has native HSTS configuration under a site's HSTS feature in IIS Manager, exposing max-age, includeSubDomains, preload, and a redirect-HTTP-to-HTTPS toggle directly. On older IIS versions, the header has to be added manually via the URL Rewrite module or a custom response header rule.

The part administrators most often get wrong is treating HSTS as a reversible setting. It isn't, practically speaking, once a browser has cached it: the browser will refuse plain HTTP for that host for the entire cached max-age, even if you later need to serve a subdomain over HTTP, roll back to an HTTP-only maintenance page, or fix a broken certificate that HSTS is now actively preventing users from bypassing. The includeSubDomains flag makes this worse by applying the policy to every subdomain, including ones you may add later without HTTPS ready. The preload flag is the most irreversible of all — it submits your domain to a hardcoded list baked into browser source code, and removal from that list, once accepted, can take months to propagate through browser release cycles.

The safe rollout sequence is: verify HTTPS works correctly across the entire domain and every subdomain you intend to cover, set a short max-age (a few minutes to an hour) without includeSubDomains or preload, monitor for any HTTP-dependent breakage, then progressively raise max-age toward the standard one-year value over subsequent deployments. Add includeSubDomains only once every subdomain is confirmed HTTPS-capable, and treat preload submission as a final, deliberate step rather than a default checkbox.

Certificate Renewal and Automating It with win-acme

Traditional commercial certificates typically run one to two years, giving administrators a wide manual renewal window: generate a new CSR (or renew against the existing key), submit it to the CA, retrieve the reissued certificate, import it into the Local Computer\Personal store, and update the site's binding to reference the new thumbprint — remembering that the http.sys binding, as covered earlier, does not automatically follow a reissued certificate with a different thumbprint.

Let's Encrypt certificates are free but valid for only 90 days, which makes manual renewal impractical at any scale and effectively requires automation. win-acme (the wacs.exe executable) is the standard ACME client for Windows/IIS environments. Run interactively, it can scan existing IIS site bindings, let you pick a site, perform HTTP-01 domain validation by temporarily serving a challenge file through the site itself, request the certificate from Let's Encrypt, install it into the certificate store, and update the IIS binding automatically — all without hand-editing anything:

wacs.exe --target iis --siteid 1 --installation iis

The more important part happens after the first run: win-acme registers a Windows Scheduled Task (visible in Task Scheduler, typically named something like "win-acme renew (\wacs.exe)") that runs daily and checks each managed certificate's expiry. By default it renews once a certificate is within roughly 30 days of expiry (the exact threshold is configurable), reruns the ACME validation, reinstalls the new certificate, and rebinds it — all unattended. This scheduled task is the piece worth verifying explicitly after setup; if it's disabled, deleted, or fails silently (commonly due to the site's HTTP-01 challenge path being blocked by a rewrite rule or authentication requirement), certificates will expire without warning. Checking wacs.exe --list periodically, or reviewing the scheduled task's run history, is a reasonable operational habit.

Disabling Weak Protocols and Ciphers

TLS protocol and cipher selection in Windows is governed by Schannel, the OS-level security provider IIS relies on — it is not something configured inside IIS itself. Legacy protocols (SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1) and weak ciphers (RC4, DES, 3DES, export-grade suites) and weak hash algorithms (MD5) remain enabled by default on many Windows Server installations for backward compatibility, and most compliance frameworks — PCI DSS among them — require TLS 1.0 and 1.1 to be disabled outright.

These settings live in the registry under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols, with a subkey per protocol version and a Server and Client key beneath each. Disabling TLS 1.0 server-side, for example, means setting Enabled to 0 (DWORD) and DisabledByDefault to 1 under ...\Protocols\TLS 1.0\Server. Cipher suite ordering and individual cipher enable/disable state live under a separate ...\SCHANNEL\Ciphers key, and cipher suite priority order is set via a Group Policy value or the Get-TlsCipherSuite/Enable-TlsCipherSuite PowerShell cmdlets.

Editing these directly is error-prone — a mistyped key name silently does nothing, and getting the client/server distinction wrong can break outbound connections the server itself makes (e.g., to a database or API over TLS). IIS Crypto from Nartac Software is the standard tool for this: it presents every protocol, cipher, hash, and key exchange algorithm as a checklist, includes a one-click "Best Practices" template that disables SSL 2.0/3.0 and TLS 1.0/1.1 while reordering ciphers to prefer forward-secrecy suites, and writes the correct registry keys for you. A reboot is required for Schannel changes to take effect, since the provider reads its configuration at boot rather than dynamically. After applying changes, verifying with an external scanner (or a tool like testssl.sh run against the site) confirms which protocols and ciphers are actually being negotiated, rather than trusting the registry state alone.

Key Takeaways

  • A certificate binding is really an http.sys registration (IP/hostname + port mapped to a certificate thumbprint), which is why netsh and PowerShell can manage it independent of the IIS Manager UI
  • SNI is what makes name-based HTTPS hosting possible; without it every distinct certificate needs a dedicated IP address
  • HSTS max-age should start small (minutes to hours) during rollout and only be raised once you're certain every subdomain and path serves valid HTTPS
  • win-acme automates the entire Let's Encrypt lifecycle — validation, installation, IIS binding updates, and scheduled renewal — removing the 90-day manual renewal burden
  • Disabling TLS 1.0/1.1 and weak cipher suites is a compliance requirement in most standards (PCI DSS included), and IIS Crypto removes the need to hand-edit Schannel registry keys

Related Articles

IIS Application Pool ManagementIIS Installation & ConfigurationIIS Logging, Performance & Troubleshooting Common ErrorsIIS URL Rewrite and ARR: Reverse Proxy Setup