Симптом
Сертификаты перестают продлеваться, и вы узнаёте об этом, только когда у клиентов молча перестаёт работать HTTPS. certbot renew --dry-run показывает Could not bind TCP port 80 — already in use. Типовая причина на сервере, где :80 постоянно держит продакшн-nginx, а конфигурационные файлы продления прописаны на authenticator = standalone. Таймер тикает, но каждое продление падает впустую.
Корень
В /etc/letsencrypt/renewal/*.conf стоит authenticator = standalone — certbot сам поднимает временный сервер на :80. Но порт постоянно занят nginx, bind не проходит, продление падает каждый раз. Конфигурационные файлы на месте, certbot.timer активен — но вхолостую.
Диагностика, ничего не меняет:
certbot renew --dry-run
grep -H authenticator /etc/letsencrypt/renewal/*.conf
Рабочие домены идут webroot и продлеваются; битые — standalone.
Фикс: перевести на webroot
nginx уже отдаёт .well-known/acme-challenge из webroot-каталога, так что:
- Снапшот:
cp /etc/letsencrypt/renewal/*.conf <snap>/. - В каждом битом
.conf:authenticator = standalone→webroot, добавитьwebroot_path = /var/www/html,и в конец — блок[[webroot_map]]со строкой<домен> = /var/www/html. certbot renew --dry-run→ должно бытьall simulated renewals succeeded.
Подводные камни
- На хосте часто два «certbot»: реальный host-бинарь (systemd
certbot.timer) и docker-контейнер certbot со своим пустым томом — контейнер ничего не продлевает, это обманка. Боевые серты лежат на хосте в/etc/letsencrypt/live/..., nginx грузит их пофайловым bind-mount. Разберитесь, кто реально продлевает. - На :80 обычно нет
default_server(он на :443) — неизвестный Host ловит первый :80-блок с 301-редиректом. Для выпуска нового домена по webroot сперва впишите домен вserver_nameacme-блока на :80 и сделайтеnginx -s reload. Проверка:curl -H "Host: <домен>" http://127.0.0.1/.well-known/acme-challenge/probeдолжен вернуть 200, не 301. - Только один certbot за раз — фоновый dry-run держит лок, параллельный
certonlyпадает с «Another instance is already running». - nginx.conf смонтирован как файл — правим host-файл, потом reload; помните про ловушку inode при bind-mount одиночного файла.
- Поставьте срок жизни сертификатов в мониторинг с алертами за 14 и за 3 дня до истечения — чтобы истечение кричало само, а не всплывало по факту отказа HTTPS.
Вывод. Standalone-authenticator несовместим с постоянно занятым :80 — продление падает молча, пока не откажет HTTPS. Переведите конфигурационные файлы продления на webroot (nginx уже отдаёт acme-challenge), проверьте dry-run, разберитесь, какой из certbot боевой, и вынесите срок жизни сертификатов в постоянный мониторинг: тихое истечение недопустимо.