# TLS certificates on Windows: win-acme, IIS binding and renewal

> Request certificates with win-acme, bind them to IIS, export PEM for Nginx, and prove renewal works before the old certificate expires.

**Tested on**

- Build: 26100.33650
- Edition: Standard / Datacenter
- Architecture: x64
- Evidence: E1
- Verified: 2026-10-29

## The short answer

On Windows/IIS, **win-acme** is the practical ACME client:

1. Create/renew a certificate.
2. Install it into the Windows store.
3. Bind it to the IIS site.
4. Schedule renewal.
5. Test renewal **before** expiry.

For Nginx, export PEM (`fullchain.pem` + `privkey.pem`) and reload Nginx after renewal.

## Install win-acme

Download the x64 zip, extract to `C:\win-acme`, and run `wacs.exe` as Administrator. The interactive menu is the safest first run:

- **N** new certificate;
- choose **Manual input** or **IIS site**;
- select the site/host;
- choose HTTP-01 (port 80) or DNS-01 (wildcard/internal);
- choose store: Windows store / PEM files;
- install to IIS;
- create the renewal task.

CLI shape (verify against `wacs --help` for your version):

```powershell
C:\win-acme\wacs.exe --target iis --host lab.example `
  --installation iis --store certificatestore
```

Do not paste a CLI from an old blog without checking the current version’s options.

## Bind to IIS

If win-acme installs to IIS, it creates/updates the HTTPS binding. Verify:

```powershell
Get-WebBinding -Name 'Lab' -Protocol https
netsh http show sslcert hostnameport=lab.example:443
```

For SNI, use a hostname:port binding and keep `SslFlags 1`. An IP-only binding cannot serve multiple certificates on one IP.

## Export PEM for Nginx

Nginx does not read the Windows store. Export:

- certificate + intermediates as `fullchain.pem`;
- private key as `privkey.pem`;
- ACL the key to the service account + Administrators.

Then:

```nginx
ssl_certificate     C:/nginx/certs/fullchain.pem;
ssl_certificate_key C:/nginx/certs/privkey.pem;
```

Reload Nginx after every renewal (`nginx -s reload`); replacing files without reload keeps the old cert in memory.

## Verify renewal

```powershell
Get-ChildItem Cert:\LocalMachine\My |
  Where-Object Subject -like '*lab.example*' |
  Select-Object Subject,NotAfter,Thumbprint
```

Check the renewal task exists and has run. Force a dry run/renewal in a maintenance window. A certificate that renews but does not rebind is still an outage.

> **Caution: Do not wait for expiry to test renewal**
>
> Test the renewal path at least one renewal cycle before expiry. Check DNS-01 credentials, HTTP-01 reachability, IIS binding and Nginx reload.

## Rate limits and wildcards

- Let’s Encrypt has rate limits; do not loop failed validations.
- Wildcards require DNS-01; HTTP-01 cannot issue `*.example.com`.
- DNS-01 credentials are powerful; store them with least privilege.
- Internal names cannot get public certificates; use an internal CA or `tls internal` (Caddy lab).

## Lab boundary

E1. The lab used a self-signed certificate for WAC and did not run win-acme; the commands are the production path to validate on Server 2025.

## Sources

- [win-acme](https://www.win-acme.com/)
- [win-acme — CLI reference](https://www.win-acme.com/reference/cli)
- [Let's Encrypt — Challenge types](https://letsencrypt.org/docs/challenge-types/)

---

Source: https://windows.homes/en/server/tls/certificates/
