wGrow
menu
Automating Let's Encrypt for IIS: most of the C# is no longer needed.
Infra & Security 12 April 2026 · 5 min

Automating Let's Encrypt for IIS: most of the C# is no longer needed.

By Scott Li ·

When a custom C# Windows Service for Let's Encrypt automation on IIS still earns its keep — and when a maintained tool is the right answer.

We once shipped a small C# Windows Service that handled the Let’s Encrypt SSL/TLS certificate lifecycle for IIS websites — using the Certes ACME library and Microsoft.Web.Administration for binding installation. We wrote it because the maintained tools at the time didn’t fit our deployment shape on a couple of client estates.

We would not write that service today. Here is the honest version.

What we’d actually use

For 95% of IIS estates, the right answer is one of:

  1. win-acme (wacs) — an actively-maintained, command-line ACME client for Windows that knows about IIS bindings, scheduled tasks, and most of what our custom service did. Free, open-source, well-tested.
  2. Certify the Web — a Windows GUI client built on top of the same ACME ecosystem. Useful where the operator is more comfortable with a UI.
  3. Front the IIS estate with a reverse proxy that handles certs — Cloudflare, Caddy, NGINX, or AWS ALB. The IIS host doesn’t manage public certs at all.

For most clients, option 3 (reverse proxy in front) is now the cleanest. The IIS host serves an internal cert; the public certificate lives one hop earlier and is renewed automatically by the proxy.

When the custom Windows Service is still right

We still use the original design in two cases:

  • Air-gapped or tightly-restricted estates where neither win-acme nor a reverse proxy is permitted, but ACME via an outbound HTTP proxy is. We have one such gov-adjacent client.
  • Estates with a quirky binding pattern — e.g. wildcard certs across multiple IIS host names with non-standard SNI handling — where the maintained tools require workarounds we can’t audit.

For both, the Certes-based Windows Service is still in production and still works.

What changed in the protocol

A few details worth flagging:

ACMEv1 is gone

ACMEv1 was deprecated and is no longer accepted by Let’s Encrypt. ACMEv2 is the only protocol now. If you have an old client (ours included a fallback to v1 originally), check its dependency versions and confirm v2.

TLS-ALPN-01 vs HTTP-01 vs DNS-01

We originally used HTTP-01 challenge handling via .well-known/acme-challenge. That still works. We more often use DNS-01 for two reasons:

  • It supports wildcard certs.
  • It doesn’t require the IIS host to be reachable from the public internet on port 80, which lets us run cleaner ingress firewall posture.

DNS-01 needs an automatable DNS provider (Route 53, Cloudflare, Azure DNS). Most of our clients have one of those.

Renewal cadence and rate limits

Let’s Encrypt rate limits have not changed dramatically. The thing that changed is operational hygiene:

  • We renew at 30 days remaining (not 14), so a failed renewal has time to retry.
  • We monitor the certificate expiry on the live binding (not just on the Windows Service’s last run), with a CloudWatch / Sentinel alarm that pages a human at 14 days remaining.
  • The renewal job logs to a centralised audit pipeline. Silent failures are the failure mode we keep seeing in client estates.

The design lessons that hold

  • A Windows Service is the right host shape for unattended renewal on Windows. Scheduled Tasks work and are simpler, but a service gives cleaner logging and healthcheck behaviour.
  • Service identity matters. Run the renewal under a least-privileged account, not LocalSystem.
  • Treat the renewal pipeline as a production workload. Audit logs, health alerts, paged failures. Not a setx -A cron line.

Writing your own service isn’t the normal first step for IIS Let’s Encrypt automation. It is a fallback for specific constraints.

— Scott Li, wGrow