--- title: win-acme HTTP-01 авто-renewal на IIS под OWIN-catch-all CMS status: live tags: [windows, iis, ruvds, letsencrypt, win-acme, acme, http-01, owin, cms, ops] related: [[ruvds-iis-host]], [[traefik-acme-json-to-iis-cert-import]], [[windows-server-2025-core-bootstrap]], [[iis-migration-2026-05-19-postmortem]] --- # win-acme HTTP-01 авто-renewal на IIS под OWIN-catch-all CMS Постоянный self-renewing LE-pipeline на RUVDS IIS-хосте ([[ruvds-iis-host]]) для сайта `snolla` (25 hostname через SNI). **Заменяет** ручной метод [[traefik-acme-json-to-iis-cert-import]] (экспорт PFX из домашнего traefik `acme.json` + ручной `Import-PfxCertificate`) — тот делал RUVDS зависимым от домашней машины по сертификатам и требовал ручного продления. Поднято 2026-06-05, после full DNS cutover на RUVDS (HTTP-01 challenge до cutover'а отскакивал на windows-source — LE не мог достучаться). ## Что развёрнуто - **win-acme v2.2.9** в `C:\win-acme\` (скачан с GitHub releases, `x64.pluggable`). - Один **SAN-cert на 25 hostname** в store `WebHosting`, установлен во все 25 `*:443:` SNI-биндинга. - **Scheduled task `win-acme-renew-snolla`** — daily 09:00 + random delay 4h, runs as **SYSTEM**, `wacs.exe --renew --baseuri https://acme-v02.api.letsencrypt.org/`. Renewal due 55 дней до expiry. - Idempotent setup-скрипт: `scripts/iis-migration-to-ruvds/03-ruvds-winacme.ps1` (фазы `download`/`app`/`probe`/`task`/`verify`). ## Главный gotcha: OWIN-catch-all жрёт `/.well-known/acme-challenge/` Сайт = **MoreThenCms** на OWIN. В корневом `web.config`: `owin:HandleAllRequests=true` + handler `Owin path="*"` + `runAllManagedModulesForAllRequests="true"` → **весь** трафик уходит в managed OWIN-pipeline. HTTP-01 токен (статический файл) отдаётся не статикой, а CMS → `404`/`500`/redirect. LE-валидация падает (`Invalid response ...: 500`). Снятие Owin-**handler**'а в дочернем `web.config` НЕ помогает — OWIN перехватывает на уровне **модуля** (HandleAllRequests). Что НЕ сработало: - web.config с `` — рерайта-то и нет, маршрутит managed-pipeline. - web.config с `` в обычном (managed) пуле — модуль всё равно жив. ### Рабочее решение: отдельное IIS-приложение в пуле «No Managed Code» `.well-known/acme-challenge` вынесен в **отдельное IIS Application** под сайтом `snolla` с app-pool'ом `acme-challenge`, у которого `managedRuntimeVersion=''` (**No Managed Code**). В таком пуле .NET/OWIN не стартует вообще — токен отдаётся нативным `StaticFileModule`. Нюанс: в дочернем web.config всё равно нужно **``** — иначе унаследованный managed Owin-handler даёт `500` (managed handler в unmanaged пуле). Плюс extensionless-mime (токены без расширения). Проверенный web.config: ```xml ``` Результат: токен отдаётся `200` для **любого** Host — даже для деградировавших CMS-тенантов (`maljarka.tandemmebel.ru`/`rimiz.ru`, которые на app-уровне отдают `502`/`404`), т.к. статика обходит CMS-роутинг. ### Патч шаблона win-acme win-acme при filesystem-валидации **перезаписывает** дочерний web.config своим шаблоном `C:\win-acme\Web_Config.xml` (а при `CleanupFolders=true` потом удаляет токен+web.config). Дефолтный шаблон делает `` + mime, но **не снимает Owin-handler** → снова `500`. Durable-фикс = **пропатчить сам шаблон** `C:\win-acme\Web_Config.xml`, добавив `` + `runAllManagedModulesForAllRequests="false"`. Трекаемая копия: `scripts/iis-migration-to-ruvds/winacme-Web_Config.xml`. Тогда на каждом renewal win-acme сам кладёт рабочий web.config в (постоянное) unmanaged-приложение, валидирует, чистит. Между renewal'ами папка пустая — трафика на неё нет. ## Процедура де-риска (повторяемо) 1. `--validationmode http-01 --validation filesystem` локальный probe токена через `localhost` + `Host:`-заголовок (обходит home-DPI middlebox). 2. **Staging** через `--baseuri https://acme-staging-v02.api.letsencrypt.org/` (НЕ `--test` — он включает интерактивные промпты, которые вешают non-interactive SSH). pemfiles store + `--installation none` — без правок IIS. 3. Прод: `--store certificatestore --installation iis --installationsiteid 1`. ## Грабли - **`--test` ≠ staging-only.** `--test` тащит интерактив («Try in default browser?», «Quit?») → виснет под SSH. Для staging без интерактива — `--baseuri `. - **pemfiles path должен существовать** заранее, иначе abort до валидации. - **Scheduled task руками, не через win-acme.** `--notaskscheduler` при выпуске; таск создаётся отдельно как SYSTEM (win-acme-овский setup может спросить креды интерактивно). win-acme потом пишет «Scheduled task not configured yet» — косметика, ищет *свой* таск. - win-acme renewal'ы раздельны по `--baseuri` (разные ConfigurationPath): staging-renewal не трогается прод-таском. Staging-renewal всё равно убран для чистоты. ## Команда боевого выпуска (reference) ``` C:\win-acme\wacs.exe --source iis --siteid 1 --validationmode http-01 --validation filesystem ^ --webroot C:\sites\snolla --store certificatestore --installation iis --installationsiteid 1 ^ --accepttos --emailaddress --force --notaskscheduler ```