Skip to main content

How to Add an SSL Certificate Binding to an IIS Website in Batch Script

Securing a website with HTTPS is no longer optional. Browsers flag HTTP sites as "Not Secure," search engines penalize unencrypted pages, and modern web standards like HTTP/2 effectively require TLS. On IIS, enabling HTTPS involves two distinct steps: adding an HTTPS binding to the website configuration and associating an SSL certificate with that binding.

In this guide, we will explore how to automate both steps from a Batch Script using appcmd.exe for the IIS binding and netsh for the certificate assignment.

Understanding the Two Components

An HTTPS binding in IIS consists of:

  1. The IIS Binding: Tells IIS to listen for HTTPS requests on a specific IP, port, and hostname. This is managed by appcmd.
  2. The SSL Certificate Assignment: Maps the certificate (identified by its thumbprint) to the IP:Port or Hostname:Port combination. This is managed by netsh http add sslcert.

Both must be configured for HTTPS to work.

Prerequisites

Before running the script, you need:

  • A valid SSL certificate installed in the Local Machine > Personal (Cert:\LocalMachine\My) certificate store.
  • The certificate's thumbprint (a 40-character hexadecimal string).
  • Administrator privileges.

Finding Your Certificate Thumbprint

@echo off
echo Available SSL Certificates in Local Machine Personal Store:
echo.

powershell -NoProfile -Command "Get-ChildItem Cert:\LocalMachine\My | Format-Table Subject, @{Name='Thumbprint';Expression={$_.Thumbprint}}, NotAfter -AutoSize"

pause
warning

When copying a certificate thumbprint from the Windows certificate manager (certmgr.msc) or from the Certificate Properties dialog, Windows often prepends an invisible Unicode left-to-right mark character (U+200E) to the thumbprint string. This hidden character causes netsh http add sslcert to fail with a misleading "parameter is incorrect" error. Always copy thumbprints from PowerShell output ((Get-ChildItem Cert:\LocalMachine\My).Thumbprint) or manually verify there are no invisible characters by retyping the value.

Method 1: Basic HTTPS Binding (IP-Based)

The traditional method binds the certificate to an IP:Port combination. This approach works on all IIS versions but limits you to one certificate per IP address on port 443.

@echo off
setlocal

set "appcmd=%SystemRoot%\System32\inetsrv\appcmd.exe"
set "site_name=SecureWebsite"
set "cert_hash=A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2"
set "app_id={4dc3e181-e14b-4a21-b022-59fc669b0914}"

:: Verify Admin
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Administrator privileges required.
pause
exit /b 1
)

:: Verify appcmd exists
if not exist "%appcmd%" (
echo [ERROR] appcmd.exe not found at: %appcmd%
echo IIS may not be installed or Management Tools are missing.
pause
exit /b 1
)

:: Verify the site exists
"%appcmd%" list site /name:"%site_name%" >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Website "%site_name%" not found in IIS.
pause
exit /b 1
)

echo Adding HTTPS binding to "%site_name%"...

:: Step 1: Add the HTTPS binding in IIS
"%appcmd%" set site /site.name:"%site_name%" /+bindings.[protocol='https',bindingInformation='*:443:'] 2>nul

if %errorlevel% neq 0 (
echo [INFO] HTTPS binding may already exist on *:443. Continuing with certificate assignment...
)

:: Step 2: Remove any existing certificate binding on this port (to avoid conflicts)
netsh http delete sslcert ipport=0.0.0.0:443 >nul 2>&1

:: Step 3: Assign the SSL certificate to the port
netsh http add sslcert ipport=0.0.0.0:443 certhash=%cert_hash% certstorename=My appid=%app_id%

if %errorlevel% equ 0 (
echo.
echo [SUCCESS] HTTPS configured for "%site_name%".
echo Site accessible at: https://your-server-name
) else (
echo.
echo [ERROR] SSL certificate binding failed.
echo Verify the thumbprint: %cert_hash%
echo Ensure the certificate is in the Local Machine Personal store.
echo Check for invisible characters in the thumbprint string.
)

pause

Understanding the Parameters

  • ipport=0.0.0.0:443: Binds the certificate to all IP addresses on port 443.
  • certhash: The 40-character certificate thumbprint (no spaces, no colons).
  • certstorename=My: The certificate store name ("My" = Personal store).
  • appid: A GUID identifying the application that owns this binding. You can generate one with powershell -Command "[guid]::NewGuid()" or reuse a consistent value across your scripts.

Server Name Indication (SNI) allows multiple HTTPS websites to share the same IP address and port (443) by using different certificates based on the requested hostname. This is the standard approach for modern IIS hosting.

@echo off
setlocal

set "appcmd=%SystemRoot%\System32\inetsrv\appcmd.exe"
set "site_name=CustomerPortal"
set "hostname=portal.example.com"
set "cert_hash=B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3"
set "app_id={5ec4f292-f25c-5b32-c133-6afd770c1a25}"

net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)

if not exist "%appcmd%" (
echo [ERROR] appcmd.exe not found. IIS may not be installed.
pause
exit /b 1
)

:: Verify the site exists
"%appcmd%" list site /name:"%site_name%" >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Site "%site_name%" not found in IIS.
pause
exit /b 1
)

echo Adding SNI HTTPS binding for %hostname%...

:: Step 1: Add HTTPS binding base (SNI isolated to comply with appcmd key-validation)
"%appcmd%" set site /site.name:"%site_name%" /+bindings.[protocol='https',bindingInformation='*:443:%hostname%'] 2>nul
if %errorlevel% neq 0 (
echo [INFO] HTTPS binding for %hostname% may already exist. Continuing...
)

:: Step 2: Enable SNI (sslFlags=1) on the newly allocated binding profile
"%appcmd%" set site /site.name:"%site_name%" /bindings.[protocol='https',bindingInformation='*:443:%hostname%'].sslFlags:1 >nul 2>&1

:: Step 3: Remove any existing certificate binding for this hostname (to avoid conflicts)
netsh http delete sslcert hostnameport=%hostname%:443 >nul 2>&1

:: Step 4: Assign the certificate using hostnameport (SNI-aware)
netsh http add sslcert hostnameport=%hostname%:443 certhash=%cert_hash% certstorename=MY appid=%app_id%

if %errorlevel% equ 0 (
echo [SUCCESS] SNI HTTPS binding configured.
echo Site accessible at: https://%hostname%
) else (
echo [ERROR] Certificate binding failed.
echo Verify thumbprint: %cert_hash%
echo Ensure the certificate is valid for hostname: %hostname%
)

pause

Key Differences from IP-Based

  • sslFlags='1': Enables SNI on this binding. Without this flag, IIS requires a unique IP per HTTPS site on the same port.
  • hostnameport= instead of ipport=: The netsh command uses the hostname instead of IP for SNI certificate matching.
tip

SNI is supported by all modern browsers and IIS 8+ (Windows Server 2012+). Use SNI unless you need to support very old clients (such as Internet Explorer on Windows XP, which is end-of-life).

Method 3: Complete HTTPS Setup Script

A production-ready script that checks for the certificate, creates the binding, assigns the cert, and verifies the result.

@echo off
title SSL Certificate Binding
setlocal enabledelayedexpansion

set "appcmd=%SystemRoot%\System32\inetsrv\appcmd.exe"

:: Configuration
set "site_name=CompanyWebsite"
set "hostname=www.company.com"
set "cert_hash=C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4"
set "app_id={6fd503a3-a36d-4c43-b244-7b0e081d2b36}"

net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)

if not exist "%appcmd%" (
echo [ERROR] appcmd.exe not found. IIS may not be installed.
pause
exit /b 1
)

echo =============================================
echo SSL CERTIFICATE BINDING SETUP
echo =============================================
echo.
echo Site: %site_name%
echo Hostname: %hostname%
echo Cert: %cert_hash%
echo.

:: 1. Verify the certificate exists in the store
echo [1/4] Verifying certificate...
set "cert_found=0"
for /f "delims=" %%C in ('powershell -NoProfile -Command "$cert = Get-ChildItem Cert:\LocalMachine\My\!cert_hash! -ErrorAction SilentlyContinue; if ($cert) { Write-Output ('Subject=' + $cert.Subject); Write-Output ('Expires=' + $cert.NotAfter.ToString('yyyy-MM-dd')) } else { Write-Output 'NOTFOUND' }" 2^>nul') do (
set "line=%%C"
if "%%C"=="NOTFOUND" (
set "cert_found=0"
) else (
echo %%C
set "cert_found=1"
)
)

if !cert_found! equ 0 (
echo [ABORT] Certificate not found in Local Machine Personal store.
echo Thumbprint: !cert_hash!
echo Verify the thumbprint and check for hidden Unicode characters.
pause
exit /b 1
)

:: 2. Verify the site exists
echo.
echo [2/4] Checking IIS site...
"%appcmd%" list site /name:"%site_name%" >nul 2>&1
if !errorlevel! neq 0 (
echo [ABORT] Site "%site_name%" does not exist in IIS.
pause
exit /b 1
)
echo Site found.

:: 3. Add HTTPS binding base (SNI isolated to comply with appcmd key-validation)
echo.
echo [3/4] Adding HTTPS binding...
"%appcmd%" set site /site.name:"%site_name%" /+bindings.[protocol='https',bindingInformation='*:443:%hostname%'] 2>nul
if !errorlevel! equ 0 (
echo Binding allocated.
) else (
echo HTTPS binding base may already exist natively. Continuing...
)

:: Enable SNI (sslFlags=1) on the newly allocated binding profile natively
"%appcmd%" set site /site.name:"%site_name%" /bindings.[protocol='https',bindingInformation='*:443:%hostname%'].sslFlags:1 >nul 2>&1

:: 4. Assign SSL certificate
echo.
echo [4/4] Binding certificate...

:: Remove any existing binding first (to avoid conflicts)
netsh http delete sslcert hostnameport=%hostname%:443 >nul 2>&1

:: Add the new binding
netsh http add sslcert hostnameport=%hostname%:443 certhash=%cert_hash% certstorename=My appid=%app_id%

if !errorlevel! equ 0 (
echo.
echo =============================================
echo [SUCCESS] HTTPS IS CONFIGURED
echo URL: https://%hostname%
echo =============================================
echo.
echo Verify with: netsh http show sslcert hostnameport=%hostname%:443
) else (
echo.
echo [ERROR] Certificate binding failed.
echo Check the System Event Log for details.
echo Common causes:
echo - Invisible Unicode characters in the thumbprint
echo - Certificate private key not exportable or missing
echo - Certificate not in Local Machine store
)

pause

Viewing Current SSL Bindings

To see which certificates are currently bound to which ports/hostnames:

@echo off
echo Current SSL Certificate Bindings:
echo ==================================
echo.
netsh http show sslcert
echo.
pause
tip

To view the binding for a specific hostname only, use:

netsh http show sslcert hostnameport=www.example.com:443

Or for an IP-based binding:

netsh http show sslcert ipport=0.0.0.0:443

Removing an SSL Certificate Binding

@echo off
setlocal

set "hostname=old.example.com"

net session >nul 2>&1
if %errorlevel% neq 0 (
echo [ERROR] Admin required.
pause
exit /b 1
)

:: Check if the binding exists before attempting removal
netsh http show sslcert hostnameport=%hostname%:443 >nul 2>&1
if %errorlevel% neq 0 (
echo [INFO] No SSL binding found for %hostname%:443. Nothing to remove.
pause
exit /b 0
)

echo Removing SSL binding for %hostname%:443...

netsh http delete sslcert hostnameport=%hostname%:443

if %errorlevel% equ 0 (
echo [OK] SSL certificate binding removed.
echo Note: The HTTPS binding in IIS may still exist.
echo To remove it, use: appcmd set site /site.name:"SiteName" /-bindings.[protocol='https',...]
) else (
echo [ERROR] Failed to remove SSL binding.
)

pause

Common Mistakes

The Wrong Way: Using Spaces or Colons in the Thumbprint

:: WRONG - Thumbprint must be continuous hex with no separators
set "cert_hash=A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2"
netsh http add sslcert ipport=0.0.0.0:443 certhash=%cert_hash%
danger

The netsh command expects a 40-character hex string with no colons, spaces, or other separators. Copy the thumbprint from PowerShell using (Get-ChildItem Cert:\LocalMachine\My).Thumbprint and strip any formatting characters. Be especially aware of invisible Unicode left-to-right mark characters (U+200E) that Windows certificate dialogs insert when you copy a thumbprint: these cause netsh to fail with a misleading error.

The Wrong Way: Certificate in the Wrong Store

:: WRONG - Certificate imported into Current User store instead of Local Machine
:: IIS runs as a system service and cannot access HKCU-based certificates

IIS runs under the SYSTEM account. It can only access certificates in the Local Machine store (Cert:\LocalMachine\My), not the Current User store (Cert:\CurrentUser\My). If your certificate was imported to the wrong store, move it using the Local Machine certificate manager (certlm.msc).

The Wrong Way: Using an Invalid GUID for appid

:: WRONG - GUIDs contain only hex digits (0-9, a-f), not letters g-z
set "app_id={6fd5g3a3-g36d-6c43-d244-7bgeh81d2b36}"
netsh http add sslcert ipport=0.0.0.0:443 certhash=... appid=%app_id%
warning

A GUID uses only hexadecimal characters (0-9 and a-f). Characters like g, h, or any letter beyond f make the GUID invalid, causing netsh to reject the command. Generate a valid GUID using:

powershell -NoProfile -Command "[guid]::NewGuid().ToString('B')"

The 'B' format specifier produces the GUID with surrounding braces (e.g., {a1b2c3d4-e5f6-7890-abcd-ef1234567890}), which is the format netsh expects.

The Wrong Way: Adding a Binding Without Removing Stale Entries

:: WRONG - If a previous certificate was bound to this port, the add command fails
netsh http add sslcert ipport=0.0.0.0:443 certhash=NEW_HASH certstorename=My appid={...}

If a previous certificate binding exists for the same IP:Port or Hostname:Port, the add command fails with "Cannot create a file when that file already exists." Always run netsh http delete sslcert before netsh http add sslcert to clear any stale entries.

Best Practices

  1. Use SNI for multi-domain hosting: It allows unlimited HTTPS sites on a single IP address and is supported by all modern browsers.
  2. Verify the certificate before binding: Always confirm the thumbprint and expiration date before attempting to bind. Use PowerShell to query the certificate store programmatically.
  3. Remove old bindings first: Use netsh http delete sslcert before adding to prevent conflicts with stale entries.
  4. Store certificates in Local Machine: IIS cannot access the Current User certificate store. Always import to Cert:\LocalMachine\My.
  5. Use consistent App IDs: Maintain a documented GUID per application for audit trail purposes. Generate valid GUIDs using PowerShell.
  6. Watch for invisible thumbprint characters: When copying thumbprints from Windows GUI tools, verify there are no hidden Unicode characters by retyping or using PowerShell output.
  7. Verify appcmd.exe exists: Check for the tool at the start of every script to provide a clear error message on systems where IIS is not installed.
  8. Verify the site exists before modifying bindings: Always confirm the target site is present in IIS before attempting to add bindings.

Conclusion

Adding an SSL certificate binding to an IIS website from a Batch Script requires coordinating two tools: appcmd for the IIS-level HTTPS binding configuration and netsh for the OS-level certificate-to-port mapping.

By adopting SNI-based bindings, modern servers can host dozens of HTTPS websites on a single IP address.

A production-quality script validates the certificate's presence, checks the site's existence, and provides clear success or failure feedback, making automated HTTPS provisioning reliable and repeatable.