Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Use Powershell to bind SSL Certificates to an IIS Host Header Site


:P
On this page:

Certificate management on Windows has always been a pain in the ass. I've been dealing with certificates a bit in the last few months as I've moved all of my sites over to Lets Encrypt, so here are a few notes on how to use command line tools, or more specifically Powershell to manage certificates in relation to IIS installations.

Things got a little more complicated with Windows Server 2012 and support for SNI which lets you bind multiple Certificates to a single IP address which means that certificates have to be bound that way as well. In researching this there have been solutions for some time for binding to IP addresses, but when binding to host headered sites with SNI, things work a little differently and finding the right solution took a bit of fiddling.

Getting Started

For these examples I'll use Powershell and the IIS WebAdministration Powershell snap-in. If you have IIS installed this snap-in should be registered, if not you may have to install it.

The process to register an SSL certificate in an IIS site is:

  • Create or import a certificate
  • Find its certificate hash
  • Bind the host header/IP address to the certificate in http.sys
  • Bind the IP/Hostheader binding to IIS

Getting Setup

Let's start by opening a Powershell Administrator window, and then creating a couple of variables in Powershell that we'll re-use with various commands:

$hostname="test.west-wind.com"
$iisSite="Default Web Site"

Cutin' and Pastin'

Note most of the following Powershell commands below include line breaks for readability but they need to be on a single line to execute. I've copied all the commands at the bottom into a parameterized script that you can either run directly or cut and paste from more easily.

Creating a Test Certificate

I'll start by creating a test certificate to bind. If you already have a certificate and that certificate is installed you can skip this step and go directly to retrieving the certificate's thumbprint/certhash.

To create a local client side certificate:

& "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\x64\makecert" 
   -r -pe -n "CN=$hostname" -b 06/01/2016 -e 06/01/2020 
   -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine  -sky exchange 
   -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12

Once created you need to also make add this certificate to your trusted root certificates so that your local machiune will trust it - without this step the certificate will show as secure but untrusted. From the start menu run Manage Certificates | Personal to find your certificate and then copy it over into the Trusted Root Certificates:

Once you've created the certificate you can check to see whether it was created with:

dir certs:\localmachine\my

From the display there you can also copy the thumbprint, which we'll need later to bind the certificate in IIS.

We'll want to capture the certificate hash (shown as the thumbprint) into a variable, so we can use to store that to a $cert variable:

$cert = (Get-ChildItem cert:\LocalMachine\My 
      | where-object { $_.Subject -like "*$hostname*" } 
      | Select-Object -First 1).Thumbprint

This should all look something like this:

Existing Certificates

If you have an existing certificate you can import it with CertUtil:

From a PFX:

certutil -importpfx <pfx_file>

From a CER:

certutil –addstore MY <Cer_File>

Get the Certificate Hash or 'Thumbprint'

Once a certificate exists you need to find the certificate hash which is used to bind the certificate to an IP address and to an IIS site.

As before browse your certs and find the thumbprint, copy it from the Powershell window and paste it to the $cert variable:

dir certs:\localmachine\my
$cert = "<thumbprintOfYourCert>"

Bind the certificate to an IP Address and Port

Once you have a certificate hash in the $cert variable I can bind the certificate to a port.

For host header support you need to use the hostnameport parameter netsh sslcert command to specify a combination for hostname and port. This is required so you can use SNI (Server Name Indication) with the IIS Site, which allows you to bind multiple certificates to a single IP address in IIS.

To bind hostname and IP to your certificate you can now do:

$guid = [guid]::NewGuid().ToString("B")
netsh http add sslcert hostnameport="${hostname}:443" certhash=$cert certstorename=MY appid="$guid"

Note that here we're binding an hostname and IP Address combination using the hostnameport option which is what's required for SNI. The hostname:port combo doesn't yet exist, but it works regardless as this just creates an entry in a mapping table.

Bind the Web Site to the Host Header IP

Finally we need to also bind Web site to the IP/Hostheader/Port combination.

New-WebBinding -name $iisSite -Protocol https  -HostHeader $hostname -Port 443 -SslFlags 1

Notice the -SslFlag=1 setting, which enables SNI on this binding.

If for whatever reason you need to remove the binding you can use:

netsh http delete sslcert hostnameport=test.west-wind.com:443

Finito

When it's all said and done we should now have a completed binding in IIS:

Note that we have the SNI setting flagged and the certificate pointing at our newly created certificate.

You should now be able to browse to your site under SSL and get the secure lock icon:

Powershell Code Snippets

As mentioned here are all the pastable Powershell commands. It's actually a runnable script which you can run.

First make sure you find your cert hash:

dir cert:\localmachine\my

# find your has and paste as value below
$cert="PastedCertificateHash"

Then you can run the script to pass in the hostname, the IIS site name, machine name and the cert hash as parameters:

# Execute script and pass the hash
.\SSLIISBinding.ps1 "test.west-wind.com" "Default Web Site" "LocalMachine" $cert

Here's the full script. Note if you don't pass a cert hash to it it will create a new local certificate with the name of the hostname.

$hostname = "test.west-wind.com"
$iisSite = "Default Web Site"
$machine = "LocalMachine"

if ($args[0]) 
{     
    $hostname = $args[0]
}
if($args[1])
{
    $iisSite = $args[1]
}
if ($args[2])
{
    $machine = $args[2]
}
if ($args[3])
{
    $cert = $args[3]
}
"Host Name: " + $hostname
"Site Name: " + $iisSite
"  Machine: " + $machine
if (-not $cert) {
    # Create a certificate
    & "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\x64\makecert" -r -pe -n "CN=${hostname}" -b 06/01/2016 -e 06/01/2020 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine  -sky exchange  -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12

    dir cert:\localmachine\my
    $cert = (Get-ChildItem cert:\LocalMachine\My | where-object { $_.Subject -like "*$hostname*" } | Select-Object -First 1).Thumbprint
    $cert
}
"Cert Hash: " + $cert

# http.sys mapping of ip/hostheader to cert
$guid = [guid]::NewGuid().ToString("B")
netsh http add sslcert hostnameport="${hostname}:443" certhash=$cert certstorename=MY appid="$guid"

# iis site mapping ip/hostheader/port to cert - also maps certificate if it exists
# for the particular ip/port/hostheader combo
New-WebBinding -name $iisSite -Protocol https  -HostHeader $hostname -Port 443 -SslFlags 1

# netsh http delete sslcert hostnameport="${hostname}:443"
# netsh http show sslcert

Related Content

Posted in IIS  Security  

The Voices of Reason


 

Richard L
February 19, 2017

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

Given that you've done this in PowerShell, a better option than MakeCert which needs to be installed is the cmdlet New-SelfSignedCertificate:

$cert = New-SelfSignedCertificate -DnsName $hostname -CertStoreLocation Cert:\LocalMachine\My -NotAfter 2020-01-01

This can be combined with retrieving the existing certificate to make your script generate a cert if needed...

$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Subject -eq "CN=$bindinghostname" }
if (!$cert) {
    $cert = New-SelfSignedCertificate -DnsName $bindinghostname -CertStoreLocation Cert:\LocalMachine\My
}

Chris
March 16, 2017

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

What if I don't want Hostname support? Like the default IIS behaviour?

So to replicate through the UI, I would click on my Site, click "Bindings" and Hostname would be greyed out but I'm able to select my cert from the drop down. I would like to do this via powershell but can't seem to figure it out.


Burton
May 01, 2017

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

Here's a link to a solution that doesn't involve calling netsh. I'm adding SSL on a site that has 5 domains with a cert that has Alternative subject names. Note that the * for IP address and passing -Thumbprint seem to be key to getting it just right.

New-WebBinding -Name $name -Protocol "https" -Port $httpsPort -HostHeader $hostname -SslFlags 1 # SNI certificate
New-Item -Path "IIS:\\SslBindings\*!$httpsPort!$hostname" -Thumbprint $thumbprint -SSLFlags 1

https://social.technet.microsoft.com/Forums/office/en-US/c83d72eb-4215-40ab-aa53-56f75ee52250/newwebbinding-syntax-to-add-ssl-binding-to-specific-iis-web-site?forum=winserverpowershell


David
January 09, 2018

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

just a note on selecting a certificate once you start using let's encrypt and will have a few certs with the same hostname. also pipe sort

.Subject -like "*$hostname*" } | sort -Property NotBefore | Select-Object -First 1).Thumbprint

and you will always get the latest and correct thumprint


Dave
June 12, 2018

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

Late to the party here in 2018, but on a new Win Server 2016, I was able to bind the certificate by grabbing a handle on the Web-binding which was just created and calling AddSslCertificate on it (certificate already imported into WebHosting store).

$binding = Get-WebBinding -Name $siteName -Protocol https  
$binding.AddSslCertificate($thumbprint, "WebHosting")

Thanks Rick!


Chris Marisic
October 30, 2020

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

Following up to Dave's answer, this can be one-lined and not require any variables at all:

(Get-WebBinding -Name "My Api" -Port 443 -Protocol "https" -HostHeader "api.my.com")
.AddSslCertificate("SSL_THUMBPRINT_HERE", "my")

The last parameter "my" will resolve to the certificate store for the local computer for the Personal\Certificates folder [typical webserver SSL installation location]. You can grab the thumbprint value by inspecting the certificate in a browser, certmanager snapin, or alternatively you can use other commands on this post/comments to get the cert via powershell and access the thumbprint as a property of it.


Graham Furner
April 26, 2021

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

Very helpful, thank you.

Although a couple of typos threw me for a while:

dir certs:\localmachine\my

should be:

dir cert:\localmachine\my


Sinethemba Paula
March 14, 2022

# re: Use Powershell to bind SSL Certificates to an IIS Host Header Site

I have issues installing and binding a certificate with powershell. The certificate installs correctly and the binding is correct as well but on binding, the SSL certificate is not selected. I get the below error:

SSL Certificate add failed, Error: 1312 A specified logon session does not exist. It may already have been terminated.

My script:

$hostname="caudaint.int.capinet"
$iisSite="Cauda.BranchView"
$secureString = convertto-securestring "pass" -asplaintext -force

Import-PfxCertificate -FilePath "D:\Data\SSL\cbcaudaappint01.int.capinet.pfx"  -CertStoreLocation Cert:\LocalMachine\My -Password $secureString

$cert = (Get-ChildItem cert:\LocalMachine\My | where-object { $_.Subject -like "*$hostname*" } | Select-Object -First 1).Thumbprint
      
$guid = [guid]::NewGuid().ToString("B")
netsh http add sslcert hostnameport="${hostname}:443" certhash=$cert certstorename=MY appid="$guid"
New-WebBinding -name $iisSite -Protocol https  -HostHeader $hostname -Port 443 -SslFlags 1
$cert

Anyone knows what could be causing this issue?


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024