How to Set Up Let’s Encrypt SSL for IIS on Windows Server 2025

Let’s Encrypt is a free, automated certificate authority that issues 90-day Domain Validation (DV) TLS certificates. On Linux with Apache or nginx, Certbot handles the entire process; on Windows with IIS, the recommended equivalent is win-acme (formerly known as letsencrypt-win-simple), a fully automated ACME v2 client that integrates natively with IIS — creating HTTPS bindings, installing certificates into the Windows certificate store, and scheduling automatic renewal. This tutorial covers downloading and configuring win-acme on Windows Server 2025, validating domain ownership via the HTTP-01 challenge, handling renewal automatically, troubleshooting common validation failures, and using the DNS-01 challenge for wildcard certificates.

Prerequisites

  • Windows Server 2025 with IIS 10 installed and a running HTTP site
  • A registered domain name with a public DNS A record pointing to the server’s public IP address
  • Inbound TCP ports 80 and 443 open in Windows Firewall and any upstream firewall/NAT
  • PowerShell 5.1 or later running as Administrator
  • Internet access from the server (Let’s Encrypt’s ACME servers must be able to reach your domain on port 80 for HTTP-01 validation)

Step 1: Downloading win-acme

win-acme is distributed as a self-contained ZIP archive. Download the latest release directly from GitHub:

# Create a permanent directory for win-acme (not a temp folder — the renewal task needs it)
$wacmeDir = "C:Toolswin-acme"
New-Item -ItemType Directory -Path $wacmeDir -Force

# Get the latest release download URL
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/win-acme/win-acme/releases/latest"
$asset   = $release.assets | Where-Object { $_.name -like "*pluggable*trimmed*win-x64*" } | Select-Object -First 1

# Download and extract
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile "$env:TEMPwacs.zip"
Expand-Archive -Path "$env:TEMPwacs.zip" -DestinationPath $wacmeDir -Force

# Confirm the executable is present
Get-Item "$wacmeDirwacs.exe"

Keep win-acme in a stable location such as C:Toolswin-acme. The scheduled renewal task that win-acme creates references the executable path — moving it after setup breaks automatic renewal.

Step 2: Running the Interactive Wizard

win-acme’s interactive wizard handles most configuration decisions through a series of prompts. Launch it from an elevated PowerShell window:

cd C:Toolswin-acme
.wacs.exe --target iis

The wizard proceeds through the following steps:

  1. Choose site — lists all IIS sites. Select the number corresponding to your site (e.g., Default Web Site).
  2. Choose host names — displays all host names bound to the site. You can include all of them or select specific ones. For a single domain like example.com and www.example.com, include both so a single certificate covers them.
  3. Validation method — select HTTP-01 via IIS. win-acme creates a temporary file in a .well-known/acme-challenge/ folder inside your site root that Let’s Encrypt’s servers will request to verify domain ownership.
  4. Certificate store — select Windows Certificate Store (My / Personal), which is where IIS reads certificates from.
  5. Installation step — select Create or update IIS bindings. win-acme will create an HTTPS binding on port 443 and configure it to use the new certificate automatically.
  6. Email address — enter a valid email for expiry notifications from Let’s Encrypt (sent when renewal is about to fail, not on every renewal).

win-acme then requests the certificate, validates the challenge, installs the cert, and updates IIS — all within the same session.

Step 3: Unattended (Scripted) Certificate Request

For automation pipelines or multiple sites, use command-line arguments instead of the wizard:

.wacs.exe `
    --target         iis `
    --siteid         1 `
    --host           "example.com,www.example.com" `
    --validation     selfhosting `
    --certificatestore My `
    --installation   iis `
    --emailaddress   "[email protected]" `
    --accepttos

Replace --siteid 1 with the IIS site ID for your site (Get-Website | Select-Object Name, Id to list them). The --accepttos flag accepts the Let’s Encrypt Terms of Service non-interactively.

Step 4: Verifying the Certificate and IIS Binding

# List certificates in the Personal store issued by Let's Encrypt
Get-ChildItem Cert:LocalMachineMy |
    Where-Object { $_.Issuer -like "*Let's Encrypt*" } |
    Select-Object Subject, NotAfter, Thumbprint

# Confirm the IIS HTTPS binding was created/updated
Get-WebBinding -Name "Default Web Site" | Format-Table Protocol, bindingInformation, certificateHash

In IIS Manager, navigate to your site > Bindings. You should see an HTTPS binding on port 443 with the Let’s Encrypt certificate selected. Test from a browser or with PowerShell:

$req = [Net.HttpWebRequest]::Create("https://example.com")
$req.GetResponse().StatusCode   # Should return OK

Step 5: Automatic Renewal via Scheduled Task

win-acme registers a Windows scheduled task named win-acme renew (win-acme A.B.C.D) that runs daily. Let’s Encrypt certificates are valid for 90 days; win-acme renews them when fewer than 55 days remain.

# Verify the scheduled task exists
Get-ScheduledTask -TaskName "win-acme*" | Select-Object TaskName, State, LastRunTime, NextRunTime

# Manually trigger a renewal check (useful for testing)
.wacs.exe --renew --verbose

# Force renewal of all certificates regardless of expiry date
.wacs.exe --renew --force

The task runs as SYSTEM by default, which has sufficient permissions to update the Windows certificate store and IIS bindings. If your site runs under a different identity, ensure SYSTEM still has read access to the site’s root for HTTP-01 validation file placement.

Step 6: DNS-01 Challenge for Wildcard Certificates

Let’s Encrypt requires DNS-01 validation to issue wildcard certificates (e.g., *.example.com). This involves creating a TXT record in DNS instead of serving a file over HTTP — which also works for servers that cannot accept inbound port 80 traffic.

# Example using the Azure DNS plugin (requires Az.Dns PowerShell module and service principal)
.wacs.exe `
    --target         manual `
    --host           "*.example.com,example.com" `
    --validation     azure `
    --azuresubscriptionid "00000000-0000-0000-0000-000000000000" `
    --azuretenantid       "00000000-0000-0000-0000-000000000001" `
    --azureclientid       "00000000-0000-0000-0000-000000000002" `
    --azureclientsecret   "YourClientSecretHere" `
    --azureresourcegroupname "dns-rg" `
    --certificatestore My `
    --installation   iis `
    --emailaddress   "[email protected]" `
    --accepttos

Other supported DNS providers include Cloudflare, Route53, GoDaddy, and generic RFC 2136 TSIG DNS update. For providers without a built-in plugin, the --validation script option lets you call a custom PowerShell script to create and clean up the TXT record:

.wacs.exe `
    --target         manual `
    --host           "*.example.com" `
    --validation     script `
    --validationscript        "C:Scriptsdns-create.ps1" `
    --validationscriptdelete  "C:Scriptsdns-delete.ps1"

Step 7: Troubleshooting Validation Errors

The most common win-acme failures and their solutions:

  • HTTP 404 during challenge — Let’s Encrypt cannot retrieve the validation file. Check that .well-knownacme-challenge is not blocked by a web.config <authorization> rule or a URL Rewrite rule that redirects all HTTP to HTTPS before the challenge file can be read. Add a rewrite exclusion:
<!-- In web.config: exclude ACME challenge from HTTP→HTTPS redirect -->
<rule name="Redirect to HTTPS" stopProcessing="true">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTPS}" pattern="off" />
    <add input="{REQUEST_URI}" pattern="^/.well-known/acme-challenge/" negate="true" />
  </conditions>
  <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
</rule>
  • Rate limit exceeded — Let’s Encrypt limits duplicate certificate requests to 5 per week per registered domain. Use the Let’s Encrypt staging environment (--baseuri "https://acme-staging-v02.api.letsencrypt.org/directory") for testing.
  • Certificate not appearing in IIS binding — run .wacs.exe --renew --force and check %ProgramData%win-acmeLog for error details.
  • TLS handshake errors after installation — ensure TLS 1.2 and 1.3 are enabled in Schannel and the server supports the cipher suites in the Let’s Encrypt certificate chain.

Conclusion

win-acme transforms Let’s Encrypt certificate management on Windows Server 2025 into a near-zero-maintenance operation. After the initial setup — roughly ten minutes using the interactive wizard — certificate requests, IIS binding updates, and 90-day renewals are handled automatically by a Windows scheduled task. Wildcard certificates extend coverage to subdomains via DNS-01 validation, and the extensive plugin ecosystem supports nearly every DNS provider. The result is a fully trusted TLS configuration at no cost, with renewal handled before certificates expire, eliminating the risk of outage caused by a forgotten manual renewal.