MAATRIX / Блог / Сертификаты продлевает какой-то скрипт: найти его раньше, чем протухнет HTTPS

Сертификаты продлевает какой-то скрипт: найти его раньше, чем протухнет HTTPS

MAATRIX

Сайт открывается по HTTPS, замочек зелёный, сертификат действующий — и это единственное, что вы про него знаете. Кто выпустил, чем продлевает и продлевает ли вообще — вопросы без ответа, потому что сервер достался в наследство: без документации, без списка cron-задач в голове, без понимания, чьих рук это дело. Работающий сегодня сертификат ничего не гарантирует на послезавтра: механизм автопродления может быть сломан уже несколько недель и просто ещё не успел это показать. Разберём, где искать скрипт, который отвечает за продление, как убедиться, что он не просто существует, а реально сработает в нужный момент, и что делать, если найти его так и не удалось.

Смотрим на сам сертификат: что он о себе рассказывает

Прежде чем копаться в cron и systemd, снимите с сертификата все данные, которые он готов отдать сам — это сузит круг поисков.

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject -dates -serial

Вывод скажет три важные вещи:

  • Issuer — кто выпустил. Let's Encrypt или R3/R10/R11/E5/E6 почти наверняка означает ACME-клиент (Certbot, acme.sh, dehydrated или встроенный ACME в Traefik/Caddy). Имя коммерческого УЦ (Sectigo, DigiCert, GlobalSign) — это либо ручная установка платного сертификата, либо панель хостинга со своей интеграцией.
  • notAfter — дата истечения. Если это Let's Encrypt, срок жизни сертификата 90 дней; посчитайте, сколько дней назад он был выпущен (notAfter минус 90) — это подскажет, когда в последний раз что-то реально продлевало сертификат, и было ли это по расписанию (обычно продление происходит за 30 дней до истечения) или "в последний момент" вручную.
  • Serial — если через несколько дней проверите его снова и он не изменится при приближении даты продления, автопродление не сработало.

Дополнительно проверьте дату изменения файла сертификата на диске, если у вас есть доступ к файловой системе:

find / -iname "fullchain.pem" -o -iname "*.crt" 2>/dev/null | xargs ls -la 2>/dev/null

Путь к файлу тоже подсказка: /etc/letsencrypt/live/... — почти всегда Certbot, ~/.acme.sh/... — acme.sh, /etc/dehydrated/... — dehydrated, путь внутри /var/lib/docker/volumes/... — сертификат выпускает контейнер, а не хост-система.

Проверяем ACME-клиентов напрямую, до всякого cron

Логичнее сначала спросить сам клиент, установлен ли он и что он думает о своих сертификатах, а не гадать по расписанию.

Certbot почти всегда доступен как бинарник:

which certbot || command -v certbot
certbot certificates

Команда certbot certificates покажет все сертификаты, которые Certbot знает о себе, их домены, срок действия и статус VALID/EXPIRED. Если сайт продлевается Certbot, но бинарника certbot нет в PATH — ищите его в /opt, в snap (/snap/bin/certbot) или внутри виртуального окружения Python (/root/.certbot/, /usr/local/certbot-venv/).

acme.sh ставится не системным пакетом, а как набор скриптов в домашней директории пользователя — обычно root, но не всегда:

find / -iname "acme.sh" -type f 2>/dev/null
ls -la ~/.acme.sh/ /root/.acme.sh/ 2>/dev/null
~/.acme.sh/acme.sh --list

Список покажет домены, ЦС (Let's Encrypt, ZeroSSL, Buypass) и дату следующего продления. Полезная деталь: у каждого домена в ~/.acme.sh/domain.com/domain.com.conf есть строка Le_NextRenewTime — unix-timestamp следующего запланированного продления. Переведите её в дату (date -d @1234567890, на macOS date -r 1234567890) — и вы точно знаете, когда клиент планирует продлить сертификат в следующий раз, независимо от того, нашли вы вызывающий его cron или нет.

dehydrated — менее популярный, но всё ещё встречающийся на старых серверах shell-клиент:

which dehydrated
ls -la /etc/dehydrated/ 2>/dev/null

Если сайт работает за реверс-прокси в Docker, продлением может заниматься сам контейнер — типичный кандидат nginx-proxy + acme-companion (бывший letsencrypt-nginx-proxy-companion) или встроенный ACME в Traefik/Caddy:

docker ps --format '{{.Names}}\t{{.Image}}' | grep -iE 'acme|letsencrypt|traefik|caddy'
docker logs <имя_контейнера> --since 720h | grep -i renew

В этом случае искать cron на хосте бессмысленно — продление происходит внутри контейнера по своему таймеру, а хостовый cron тут ни при чём. Тонкости выбора между Certbot и acme.sh, если решите заменить найденный механизм на понятный, разобраны в статье «Certbot или acme.sh: что выбрать для сервера».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Если клиент не отвечает сам за себя — ищем, кто его вызывает

Клиент установлен, но сам он ничего не запускает по расписанию — кто-то должен вызывать его периодически. Стандартная логика "посмотрел crontab -l — ничего не нашёл" здесь не работает: значимая часть автопродлений спрятана вне crontab текущего пользователя.

Проверьте по порядку:

# crontab каждого пользователя в системе, не только текущего
for u in $(cut -f1 -d: /etc/passwd); do
  echo "=== $u ==="; crontab -l -u "$u" 2>/dev/null
done

# системные каталоги cron
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
grep -ril -E 'cert|acme|letsencrypt|renew' /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly 2>/dev/null

# отдельный файл /etc/crontab
cat /etc/crontab

Пакетная установка Certbot через apt на многих дистрибутивах исторически кладёт задачу именно в /etc/cron.d/certbot, а не в crontab root — это первое место, которое стоит проверить отдельно. Полный список мест, где вообще может прятаться периодическая задача — включая anacron, /var/spool/cron/, systemd path units и обёртки вроде flock/run-parts, — собран в статье «Чужая задача в cron: где ещё смотреть, кроме crontab».

Проверяем systemd timers отдельно от cron

На современных дистрибутивах (начиная примерно с Debian 10 / Ubuntu 18.04 и новее) Certbot всё чаще ставится не с cron-задачей, а с systemd timer'ом, и его легко пропустить, если смотреть только в cron.

systemctl list-timers --all

В списке ищите certbot.timer, acme.timer или таймер с любым другим говорящим именем. Если он есть — посмотрите, что именно он запускает и с каким расписанием:

systemctl cat certbot.timer
systemctl cat certbot.service

Колонки NEXT и LAST в выводе list-timers — это готовый ответ на вопрос "сработает ли автопродление и когда": NEXT показывает дату следующего запуска, LAST — результат предыдущего. Если LAST показывает срыв (failed) или дата в NEXT пуста, значит таймер зарегистрирован, но не активен (systemctl is-enabled и is-active покажут точнее) — рабочая заготовка есть, а сам механизм не работает.

Если сервер управляется control-панелью — cPanel, Plesk, ISPConfig, HestiaCP, — учитывайте, что панель может продлевать сертификаты своим внутренним планировщиком, который не виден ни в crontab, ни в systemctl list-timers. У cPanel это AutoSSL (логи в /usr/local/cpanel/logs/autossl.log), у Plesk — расширение Let's Encrypt со своей задачей и логами в /var/log/plesk/. Если на сервере стоит панель — сначала загляните туда, а уже потом в системный cron.

Механизм найден — теперь проверяем, что он реально сработает

Найти файл со скриптом — это половина дела. Скрипт может существовать, но давно не выполняться из-за смены путей после миграции, истёкшего API-токена DNS-провайдера для DNS-01 challenge, занятого порта 80 или banальной опечатки в переменной окружения, которую никто не заметил, потому что до истечения было ещё далеко.

Для Certbot есть штатный dry-run, который проходит весь цикл продления, обращаясь к staging-серверу Let's Encrypt, но не подменяет боевой сертификат:

certbot renew --dry-run

Важный нюанс: по умолчанию certbot renew (в том числе с --dry-run) пропускает сертификаты, до истечения которых больше 30 дней — вы получите "No renewals were attempted" и ничего не проверите. Чтобы прогнать полный цикл прямо сейчас независимо от реальной даты истечения, добавьте --force-renewal:

certbot renew --dry-run --force-renewal

Чистый вывод заканчивается строкой вроде Congratulations, all simulated renewals succeeded. Любая ошибка здесь — это ровно то, что сломается по-настоящему в день реального продления, только без последствий для боевого сертификата.

Для acme.sh отдельного dry-run для продления нет, но можно посмотреть на реальную историю: лог последнего запуска обычно пишется туда, куда указывает cron-задача (часто с редиректом в /root/.acme.sh/acme.sh.log), и уже упомянутый Le_NextRenewTime в конфиге домена. Если хотите проверить именно механизм выпуска (а не продления существующего), можно тестово выпустить сертификат через staging-окружение ACME с флагом --staging, не трогая боевой сертификат.

Отдельно проверьте hook, который перезагружает веб-сервер после продления — сам сертификат обновится, но старый процесс nginx/apache может продолжать отдавать старый файл из памяти, если после продления никто не выполнил reload. У Certbot это renew_hook/deploy_hook в файле /etc/letsencrypt/renewal/<домен>.conf:

grep -A2 hook /etc/letsencrypt/renewal/*.conf

Если строки с hook нет вовсе — продление отработает "успешно", но сайт продолжит отдавать старый сертификат до ручного systemctl reload nginx, и вы узнаете об этом только когда старый файл истечёт.

Скрипт не находится, а сертификат скоро истечёт

Если после всех проверок автопродление так и не обнаружено, а до истечения остаются дни, а не недели — не тратьте оставшееся время на дальнейшие раскопки. Порядок действий:

  1. Продлите сертификат вручную прямо сейчас, не дожидаясь идеального понимания причины. Если Certbot установлен, но неясно, что его вызывает:
   certbot certonly --nginx -d example.com -d www.example.com

Плагин --nginx сам временно откроет нужный конфиг для ACME-challenge и перезагрузит веб-сервер. Если nginx-плагина нет или сайт работает не через nginx — используйте --webroot -w /var/www/example.com с указанием реального корня сайта, куда ACME-сервер сможет достучаться по HTTP.

  1. Не продолжайте полагаться на найденный (или ненайденный) чужой механизм. Даже если старый скрипт в итоге нашёлся — если вы не до конца понимаете, что он делает и почему, разумнее развернуть рядом новый, понятный вам механизм автопродления и постепенно вывести старый из эксплуатации, а не чинить код, логику которого никто не может объяснить. Если решаете, каким клиентом заменять — сравнение вариантов в статье про Certbot и acme.sh выше пригодится.
  1. Настройте независимый от самого сервера мониторинг истечения сертификата — внешнюю проверку, которая дёргает порт 443 снаружи по расписанию и не зависит от того, жив ли cron внутри этого же сервера. Смысл в том, что если завтра снова что-то сломается молча, вы узнаете об этом за недели, а не в момент, когда посетители увидят предупреждение браузера. Как это настроить и какие сервисы для этого подходят — в статье «Мониторинг сертификатов и доменов: чтобы не протухло в пятницу».

Если же после ручного продления и разбора выясняется, что автопродление в принципе было сломано давно — то есть проблема не в том, что скрипт спрятан, а в том, что он реально не работает, — отдельный разбор типичных причин и починка описаны в статье «Let's Encrypt: сертификат не обновляется автоматически».

Как закрепить результат, чтобы больше не искать вслепую

Найти механизм один раз — не решение, если через полгода вы (или следующий человек с доступом к серверу) снова окажетесь в той же ситуации. Три вещи, которые стоит зафиксировать сразу после того, как разобрались:

  • Один файл-заметка на сервере/root/README-certs.md или аналогичный, с текстом: какой ACME-клиент используется, где лежит задача (cron/systemd timer), какой у неё hook, когда истекает текущий сертификат и когда должно сработать следующее продление. Звучит просто, но именно отсутствие такой заметки и создаёт задачу, которую вы только что решали.
  • Явное имя для задачи, если переносите её из безымянного /etc/cron.d/xxx в systemd timer — certbot-renew.timer понятнее, чем строка в crontab без комментариев, и следующий администратор увидит её сразу в systemctl list-timers.
  • Алерт, а не просто лог. Успешный dry-run сегодня не гарантирует, что через месяц не истечёт токен DNS-провайдера или не поменяется IP. Внешний мониторинг из предыдущего раздела и алерт в почту/мессенджер за 2-3 недели до истечения — единственная защита, которая не зависит от того, вспомните ли вы вообще про этот сервер вовремя.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Сертификат выпущен Let's Encrypt, но ни Certbot, ни acme.sh на сервере не установлены — что происходит?

Скорее всего продлевает не сам сервер, а внешняя система: панель со своей интеграцией, CI/CD-пайплайн, который пушит сертификат через scp/rsync/Ansible с другого хоста, или CDN/балансировщик перед сервером, терминирующий TLS сам. Проверьте /var/log/auth.log или /var/log/secure на регулярные SSH/SCP-подключения с одного IP примерно раз в 60-90 дней — это след внешнего управления.

certbot certificates показывает сертификат как EXPIRED, хотя браузер видит его действующим — как так?

Certbot хранит собственную запись о сертификатах в /etc/letsencrypt, и она может устареть или относиться к домену, который давно обслуживается иначе. Проверяйте реальный сертификат на сокете (openssl s_client, команда из начала статьи), а не только вывод клиента — они не всегда совпадают, особенно если сертификат когда-то переносили или ставили руками поверх.

Нашёл сразу два механизма продления — старый cron и новый systemd timer. Опасно ли это?

Само по себе нет: ACME-клиенты обычно идемпотентны и не трогают сертификат, если до истечения ещё далеко. Риск — если оба используют разные аккаунты или DNS-плагины и оба одновременно пытаются продлить сертификат: возможны конфликты за блокировки или превышение лимита запросов Let's Encrypt на домен. Отключите один из них осознанно, обычно оставляют более новый systemd timer.

Dry-run прошёл успешно, но я не уверен, что hook действительно перезагружает нужный сервис — как проверить?

Запустите hook вручную тем же способом, каким его вызывает Certbot — это отдельный shell-скрипт, путь к которому виден в renew_hook файла /etc/letsencrypt/renewal/<домен>.conf. Выполните его от того же пользователя, от которого работает cron/timer, и проверьте systemctl status nginx на время последнего reload сразу после этого.

Есть ли способ найти "скрипт продления", если он не похож ни на один известный ACME-клиент — например, самописный?

Ищите по поведению, а не по имени: grep -rl -E 'letsencrypt|acme-challenge|\.well-known' /etc/cron.d /etc/systemd/system /opt /usr/local/bin /usr/local/sbin 2>/dev/null найдёт скрипты, которые упоминают ACME-специфичные пути вроде .well-known/acme-challenge, даже если сам скрипт написан руками и не использует ни Certbot, ни acme.sh.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →