Содержание
- Насколько на самом деле серьёзен сбой из-за сертификата?
- Почему автоматического продления недостаточно?
- Как проверить срок сертификата из командной строки?
- Как поймать случай «продлили, но не перезагрузили»
- Какие пороги должно использовать оповещение о сертификате?
- Как превратить это в оповещение, которое до меня дойдёт?
- Шаг 1 — Создайте два канала, а не один
- Шаг 2 — Запустите проверку
- Шаг 3 — Поставьте на расписание и оповещайте, когда падает само расписание
- Шаг 4 — Разделите лестницу условиями
- Шаг 5 — Проверьте, прежде чем доверять
- А если мой мониторинг уже проверяет сертификаты?
- Чего Echobell не делает
- Частые вопросы
- Let's Encrypt действительно перестал слать письма об истечении?
- Сколько действуют TLS-сертификаты в 2026 году?
- Нужны ли оповещения об истечении, если я использую ARI?
- А как быть с сертификатами не на веб-сервере?
- Не превратится ли ежедневная проверка в шум?
- Может ли вся команда получать оповещение о сертификате?
- Работает ли это на macOS?
- Безопасно ли отправлять данные о сертификате в уведомлении?
- Читайте также
Под чужой и вашей инфраструктурой сертификатов изменились две вещи, и большинство команд не подстроилось ни под одну.
Первое: страховочная сетка убрана. Let's Encrypt закрыл сервис уведомлений об истечении 4 июня 2025 года — те самые письма, которые тихо спасали тысячи сайтов, когда ломалась автоматика. Обоснование было разумным (большинство подписчиков продлевает автоматически, хранить миллионы адресов — это риск для приватности, а сервис стоил «десятки тысяч долларов в год»), и заканчивается объявление советом найти сторонний мониторинг (Let's Encrypt). Многие прочли, согласились — и вторую половину так и не сделали.
Второе: запас прочности схлопнулся. С 15 марта 2026 года публичный TLS-сертификат действует максимум 200 дней, с 15 марта 2027 года — 100 дней, с 15 марта 2029 года — 47 дней, согласно голосованию SC-081v3 в CA/Browser Forum. Let's Encrypt движется быстрее потолка: шестидневные сертификаты стали общедоступны 15 января 2026 года, а план переводит профиль по умолчанию на 64 дня в феврале 2027-го и на 45 дней в феврале 2028-го (Let's Encrypt).
Оба изменения толкают в одну сторону. Продление происходит чаще — значит, у него больше шансов сломаться, а когда оно ломается, писем больше никто не шлёт. Это руководство — та самая проверка, которая закрывает разрыв: протестированный shell-скрипт, пороги, осмысленные для коротких сертификатов, и способ сделать так, чтобы последний случай звонил на телефон через Echobell.
Насколько на самом деле серьёзен сбой из-за сертификата?
Настолько, что более трети организаций столкнулись с ним за последний год. В отчёте DigiCert «2026 Global Certificate Management Outlook» — опрос Propeller Insights среди 1001 руководителя в области ИТ и кибербезопасности в США, Великобритании и Австралии, проведённый в мае 2026 года — более трети организаций сообщили о простое сервиса из-за истёкшего сертификата за прошедший год. Почти три четверти набрали не менее пяти часов простоя, связанного с сертификатами, каждая пятая — 25 часов и более, а почти каждая четвёртая заявила, что самый серьёзный инцидент обошёлся более чем в 250 000 долларов (DigiCert).
Самое интересное в этих цифрах — длительность. Пять часов — это не то, сколько занимает продление; продление занимает секунды. Пять часов — это сколько ушло у кого-то на то, чтобы узнать.
Почему автоматического продления недостаточно?
Потому что автоматика продления падает молча, а переставший запускаться cron не выдаёт вообще никакого вывода. Каждый из этих случаев реален и част, и ни один не издаёт звука:
- Таймер больше не запускается. Обновление дистрибутива, пересборка контейнера или
systemctl disableтри месяца назад, о котором никто не помнит. Не срабатывающийcertbot.timerвыглядит ровно так же, как успешно срабатывающий. - Продление прошло, но сервис так и не перечитал конфигурацию. Новый сертификат лежит на диске; nginx, HAProxy или Postfix держит в памяти старый. Это самый частый способ, которым «полностью автоматизированная» установка всё равно протухает.
- Сломался путь проверки. Кто-то добавил редирект, правило WAF или
Denyперед/.well-known/acme-challenge/, и HTTP-01 падает. Или истёк API-токен DNS-провайдера, использовавшийся для DNS-01. - Продление произошло только на одном узле. Два балансировщика, один cron. Второй продолжает отдавать старый сертификат — пока не перестанет.
- Сертификат вообще не на веб-сервере. Внутренние клиенты mTLS, брокер Kafka, LDAP-сервер, VPN-концентратор, push-сертификат системы управления устройствами. Из публичного интернета его не видно, и ни один ACME-клиент им не управляет.
- Продление зашито на неверный интервал. Let's Encrypt говорит об этом прямо: «продление с жёстко заданным интервалом в 60 дней больше не будет достаточным», когда профиль по умолчанию опустится до 64, а затем до 45 дней (Let's Encrypt).
Последний пункт стоит подчеркнуть: именно в этот сбой календарь сейчас заводит всех. Если ваш ритм продления — это число, вбитое кем-то в 2022 году, то срок жизни сертификата теперь сам идёт этому числу навстречу.
Как проверить срок сертификата из командной строки?
Один конвейер openssl, без зависимостей. Для живого хоста:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -startdate -enddate
Для файла на диске:
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -enddate
Флаг -servername на общем IP не опционален: без SNI вы получите сертификат, который сервер считает своим по умолчанию, а это может быть не тот, о котором вы беспокоитесь.
Если нужен ответ «да/нет», разбор дат можно пропустить полностью. openssl x509 -checkend <секунды> завершается с кодом 0, если сертификат переживёт это окно, и 1, если истекает внутри него (включая уже истёкший):
openssl x509 -in fullchain.pem -noout -checkend $((14 * 86400)) \
|| echo "истекает менее чем через 14 дней"
Этот контракт кодов возврата и есть весь примитив мониторинга. Всё дальнейшее — лишь обвязка вокруг него.
Как поймать случай «продлили, но не перезагрузили»
Сравните то, что на диске, с тем, что реально отдаётся. Это проверка, которую почти никто не делает, и именно она ловит сбой, невидимый для самой автоматики продления:
served=$(openssl s_client -connect 127.0.0.1:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256)
ondisk=$(openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -fingerprint -sha256)
[ "$served" = "$ondisk" ] || echo "сервис отдаёт устаревший сертификат — нужна перезагрузка"
Обе команды печатают одинаковый формат sha256 Fingerprint=AB:CD:..., так что достаточно обычного сравнения строк. Запускайте через несколько минут после окна вашего таймера продления.
Какие пороги должно использовать оповещение о сертификате?
Доли срока жизни сертификата, а не фиксированные дни. Правило «предупредить за 30 дней» было разумным для 90-дневных сертификатов. Применительно к 47-дневному оно срабатывает на совершенно здоровом сертификате; применительно к 6-дневному — звенит постоянно.
Привяжите лестницу к точке продления. Let's Encrypt рекомендует продлевать «примерно на двух третях срока жизни текущего сертификата» — значит, оставшаяся треть срока и есть момент, когда продление уже должно было произойти. Всё, что после, — свидетельство того, что оно не произошло:
| Остаток срока | Что это значит | Тип уведомления |
|---|---|---|
| 1/3 | Окно продления открылось | Ничего — это норма |
| 1/6 | Окно пропущено один раз | Обычный пуш |
| 1/12 | Продление не опаздывает, а падает | Срочное |
| < 1/24, истёк или недоступен | До сбоя — часы | Звонок |
В конкретных числах для 47-дневного сертификата это примерно так: тишина до 7,8 дня, пуш на 3,9 дня, срочное на 2 днях, звонок менее чем за сутки. Для 90-дневного: 15 дней, 7,5 дня, 3,75 дня. Для 6-дневного счёт идёт на часы, и человеческая лестница перестаёт быть подходящим инструментом — опирайтесь на ACME Renewal Information (ARI, опубликован как RFC 9773), где удостоверяющий центр сам сообщает клиенту, когда продлевать, и оповещайте только о повторяющихся сбоях клиента.
Вычисление доли — четыре строки, и оно делает один и тот же скрипт корректным для всех ваших сертификатов независимо от издателя:
to_epoch() {
date -u -d "$1" +%s 2>/dev/null || date -u -j -f '%b %d %T %Y %Z' "$1" +%s 2>/dev/null
}
pct_left() { # читает PEM со stdin, печатает процент оставшегося срока
local pem nb na
pem=$(cat)
nb=$(to_epoch "$(printf '%s' "$pem" | openssl x509 -noout -startdate | cut -d= -f2)")
na=$(to_epoch "$(printf '%s' "$pem" | openssl x509 -noout -enddate | cut -d= -f2)")
echo $(( (na - $(date -u +%s)) * 100 / (na - nb) ))
}
Первая форма date — GNU, вторая — BSD/macOS; || выберет ту, что есть у вас.
Как превратить это в оповещение, которое до меня дойдёт?
Echobell превращает вебхук или письмо в обычный пуш, срочное уведомление или настоящий звонок, который пробивается сквозь режим фокусирования и «Не беспокоить» (см. как обойти режим фокусирования в iOS). Для сертификатов это важно, потому что последняя ступень таблицы выше — как раз та, на которую вы наступите в три часа ночи в воскресенье.
Шаг 1 — Создайте два канала, а не один
Создайте канал в приложении, задайте тип уведомления Срочное и назовите его «Сертификаты истекают». Создайте второй с типом Звонок и названием «Сертификат вот-вот истечёт». Скопируйте URL вебхука из деталей каждого канала; он выглядит как https://hook.echobell.one/t/<channel-token>. Относитесь к ним как к секретам: тот, у кого есть URL звонкового канала, может позвонить вам (руководство по вебхукам).
Составьте шаблоны так, чтобы по уведомлению можно было действовать прямо с заблокированного экрана:
Заголовок: TLS {{state}}: {{host}}
Текст: осталось {{daysLeft}} дн., истекает {{notAfter}} — выдан {{issuer}}
Любой ключ JSON, который вы отправите, становится переменной (шаблоны).
Шаг 2 — Запустите проверку
Это скрипт из разделов выше, собранный воедино и протестированный от начала до конца. Сохраните его как /usr/local/bin/cert-watch:
#!/usr/bin/env bash
# cert-watch — отправляет POST в Echobell, когда TLS-сертификат близок к истечению.
set -uo pipefail
HOOK="${ECHOBELL_CERT_HOOK:?задайте ECHOBELL_CERT_HOOK — URL вебхука вашего канала}"
WARN_DAYS="${WARN_DAYS:-14}"
post() {
curl -sS -m 10 -X POST "$HOOK" \
-H 'content-type: application/json' \
-d "{\"host\":\"$1\",\"daysLeft\":$2,\"notAfter\":\"$3\",\"state\":\"$4\"}" \
>/dev/null
}
days_left() {
local end
end=$(date -u -d "$1" +%s 2>/dev/null) ||
end=$(date -u -j -f '%b %d %T %Y %Z' "$1" +%s 2>/dev/null) || return 1
echo $(( (end - $(date -u +%s)) / 86400 ))
}
check() {
local host="$1" port="$2" pem state not_after days
pem=$(openssl s_client -connect "$host:$port" -servername "$host" \
</dev/null 2>/dev/null | openssl x509 2>/dev/null)
if [ -z "$pem" ]; then
post "$host" 0 "" "unreachable"
return
fi
if printf '%s' "$pem" | openssl x509 -noout -checkend 0 >/dev/null 2>&1; then
printf '%s' "$pem" | openssl x509 -noout -checkend $((WARN_DAYS * 86400)) >/dev/null 2>&1 && return 0
state="expiring"
else
state="expired"
fi
not_after=$(printf '%s' "$pem" | openssl x509 -noout -enddate | cut -d= -f2)
days=$(days_left "$not_after") || days=-999
post "$host" "$days" "$not_after" "$state"
}
for target in "$@"; do
case "$target" in
*:*) check "${target%:*}" "${target##*:}" ;;
*) check "$target" 443 ;;
esac
done
Он сообщает три состояния — expiring, expired и unreachable — и молчит, когда всё в порядке. Цели задаются как host или host:port, поэтому не-веб сертификаты тоже покрыты:
ECHOBELL_CERT_HOOK="https://hook.echobell.one/t/<token>" \
cert-watch example.com api.example.com mail.example.com:993 ldap.internal:636
unreachable намеренно сделан оповещением, а не тихим пропуском. Проверка, которая трактует «я не смог посмотреть» как «всё хорошо», и есть первопричина того, что сертификаты истекают.
Шаг 3 — Поставьте на расписание и оповещайте, когда падает само расписание
Раз в сутки достаточно для сертификатов от 45 дней; дважды в сутки — если вы используете 6-дневные. Таймер systemd:
# /etc/systemd/system/cert-watch.service
[Unit]
Description=TLS certificate expiry check
[Service]
Type=oneshot
EnvironmentFile=/etc/echobell.env
ExecStart=/usr/local/bin/cert-watch example.com api.example.com mail.example.com:993
# /etc/systemd/system/cert-watch.timer
[Unit]
Description=Daily TLS certificate expiry check
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true важен: без него машина, выключенная в назначенное время, просто пропустит этот запуск.
Затем замкните контур на самой проверке. Юнит Type=oneshot, завершившийся ненулевым кодом, запускает OnFailure=, так что один drop-in заставит сломавшееся продление заявить о себе:
# /etc/systemd/system/certbot.service.d/echobell.conf
[Unit]
OnFailure=echobell-alert@%n.service
# /etc/systemd/system/echobell-alert@.service
[Unit]
Description=Echobell alert for %i
[Service]
Type=oneshot
EnvironmentFile=/etc/echobell.env
ExecStart=/usr/local/bin/echobell-notify %i
Где /usr/local/bin/echobell-notify — три строки:
#!/usr/bin/env bash
curl -sS -m 10 -X POST "$ECHOBELL_CERT_HOOK" \
-H 'content-type: application/json' \
-d "{\"host\":\"$(hostname -f)\",\"state\":\"renewal-failed\",\"unit\":\"$1\",\"daysLeft\":-1}"
certbot renew завершается ненулевым кодом, если хоть одно продление не удалось, — это именно тот сигнал, который вам нужен, и именно тот, который сейчас никуда не уходит. Учтите: --deploy-hook в certbot выполняется только при успехе, поэтому для этого он не годится — путь отказа должен приходить со стороны юнита.
Шаг 4 — Разделите лестницу условиями
Оба канала получают одну и ту же полезную нагрузку; какой из них действительно сработает, решают условия. На срочном канале:
state == "expiring" && daysLeft > 3
На звонковом канале:
state == "expired" || state == "unreachable" || state == "renewal-failed" || daysLeft <= 3
Обратите внимание: <= приводит обе стороны через Number(), поэтому daysLeft, отправленный строкой, всё равно сравнивается численно. В условиях нет оператора «содержит» — именно поэтому скрипт шлёт явное поле state, а не свободный текст, который пришлось бы разбирать по шаблону.
Шаг 5 — Проверьте, прежде чем доверять
Направьте скрипт на хост с заведомо плохим сертификатом и посмотрите, придёт ли оповещение:
ECHOBELL_CERT_HOOK="https://hook.echobell.one/t/<token>" cert-watch expired.badssl.com
Делайте это с включённым «Не беспокоить» на том телефоне, который действительно будет принимать, и включите в приложении Повтор неудавшегося звонка, чтобы подавленный фокусированием вызов был набран снова. Путь эскалации, который вы ни разу не запускали, — это догадка.
А если мой мониторинг уже проверяет сертификаты?
Тогда подключите его существующий вебхук к каналу и пропустите скрипт. Большинство систем мониторинга уже знают дату истечения; чего им обычно не хватает — это пути, который переживёт спящего человека.
- В Uptime Kuma есть встроенное уведомление об истечении сертификата — направьте его на канал (руководство по Uptime Kuma, настройка звонков).
- Prometheus + Alertmanager с blackbox exporter дают
probe_ssl_earliest_cert_expiry; повесьте на него правило и маршрутизируйте в канал (руководство по Prometheus, звонки через Alertmanager). - Правила оповещений Grafana шлют POST прямо в канал (руководство по Grafana).
- Upptime и UptimeRobot закрывают публичные эндпоинты (Upptime, UptimeRobot).
- Всё, что умеет только письма — портал самого УЦ, уведомления ACM у облачного провайдера, внутренний PKI — решается правилом пересылки. У каждого канала свой адрес, а
from,to,subject,textиhtmlдоступны как переменные (почтовые триггеры).
Единственное, чего ни один из этих вариантов не заменяет, — запуск проверки извне машины, которая отдаёт сертификат. Если ваш мониторинг живёт на том же хосте, сбой, уронивший хост, унесёт с собой и оповещение.
Чего Echobell не делает
Точность здесь важна, потому что управление сертификатами — категория, полная продуктов, которые умеют гораздо больше.
Echobell делает: превращает вебхук или письмо в обычный пуш, срочное уведомление или звонок; фильтрует условиями; форматирует шаблонами; доставляет один триггер каждому подписчику общего канала, и каждый сам выбирает срочность.
Echobell не делает:
- Не обнаруживает и не инвентаризует ваши сертификаты. Он не сканирует сеть, не обходит логи прозрачности сертификатов и не расскажет про сертификат, о выпуске которого никто не помнит. Скрипт выше проверяет только перечисленные вами хосты. Разрастание сертификатов — реальная проблема, и это не её решение.
- Ничего не продлевает. У него нет ACME-клиента и доступа к вашим ключам. Он сообщает, что продление сломалось; чинить по-прежнему вам.
- Не проверяет сертификаты по собственному расписанию. Размещённых проб нет. Смотреть должно что-то, что запускаете вы: таймер, задача CI, ваш существующий мониторинг.
- Не даёт дежурных ротаций, политик эскалации и подтверждений. Нет «если за пять минут никто не ответил, звони следующему». Если это нужно, вам нужна платформа управления инцидентами — см. сравнение альтернатив Opsgenie.
- Не гарантирует доставку. Звонок зависит от инфраструктуры пушей, сети и заряженного телефона. Он сокращает расстояние между поломкой и осознанием, но это не мера контроля, на которую можно опереться абсолютно.
Частые вопросы
Let's Encrypt действительно перестал слать письма об истечении?
Да. Сервис уведомлений завершился 4 июня 2025 года, и Let's Encrypt удалил адреса, хранившиеся вместе с записями о выпуске. В объявлении рекомендуется сторонний мониторинг и в качестве одного из вариантов упоминается Red Sift Certificates Lite — бесплатно до 250 сертификатов. Если вы больше года не получали от Let's Encrypt предупреждений об истечении, причина в этом, а не в том, что ничто не подходило к сроку.
Сколько действуют TLS-сертификаты в 2026 году?
Максимум 200 дней для публично доверенных TLS-сертификатов — с 15 марта 2026 года. Потолок опускается до 100 дней 15 марта 2027-го и до 47 дней 15 марта 2029-го согласно голосованию SC-081v3. Отдельные УЦ выпускают с запасом ниже потолка: DigiCert, например, выпускает 199-дневные сертификаты именно «чтобы не превысить максимально допустимый срок действия». Let's Encrypt идёт дальше и быстрее по собственному графику: шестидневные сертификаты общедоступны с января 2026 года.
Нужны ли оповещения об истечении, если я использую ARI?
Нужны, но по другим событиям. ARI (RFC 9773) сообщает вашему ACME-клиенту, когда продлевать, и полностью убирает сбой с жёстко заданным интервалом — но не гарантирует, что продление удастся, что сервис перечитает конфигурацию и что клиент вообще ещё жив. Оповещайте о повторяющихся неудачах продления и о расхождении между отдаваемым сертификатом и тем, что лежит на диске, а не об обратном отсчёте, которым вам больше не нужно управлять.
А как быть с сертификатами не на веб-сервере?
Именно они истекают чаще всего: за ними не следит ни один ACME-клиент, и ни один браузер не пожалуется, пока что-нибудь не сломается. Скрипт принимает host:port, так что IMAP на 993, LDAPS на 636, брокер Kafka на 9093 или внутренний API на 8443 работают точно так же. Сертификаты, которые никогда не касаются сокета — подпись кода, push-сертификаты, клиентские сертификаты в парке устройств — требуют вытащить дату оттуда, где они живут, и отправить её в тот же канал.
Не превратится ли ежедневная проверка в шум?
Нет, если она молчит, когда всё в порядке, — именно поэтому скрипт ничего не шлёт выше порога. Шумная конструкция — та, которая каждый день докладывает «сертификат в порядке»: через две недели её никто не читает, а в день, когда она перестанет приходить, никто этого не заметит. Если нужен heartbeat, положите его на отдельный обычный канал и никогда — на тот, что звонит. Общая версия этого рассуждения — в борьбе с усталостью от оповещений.
Может ли вся команда получать оповещение о сертификате?
Да. Поделитесь каналом — и триггер получит каждый подписчик, каждый со своим типом уведомления. Рабочее разделение: дежурный подписывается на звонковый канал, остальные — на срочный, чтобы истечение в три часа ночи будило одного человека, а не шестерых.
Работает ли это на macOS?
Работает, с одной оговоркой: BSD-овский date не понимает -d, поэтому days_left и to_epoch сначала пробуют форму GNU, а затем откатываются к date -u -j -f. openssl в macOS по умолчанию — LibreSSL, и -checkend с -fingerprint ведут себя одинаково. Если поставите OpenSSL из Homebrew, ничего не изменится.
Безопасно ли отправлять данные о сертификате в уведомлении?
Используемые здесь поля — имя хоста, дата истечения, издатель — публичны; любой может прочесть их с вашего сервера той же командой openssl. Не расширяйте нагрузку приватными ключами, внутренними путями или чем-либо из сертификата на непубличном хосте сверх имени хоста. Echobell хранит содержимое и историю уведомлений только на вашем устройстве, оставляя на сервере лишь учётные записи, каналы и подписки (модель приватности) — это хорошее значение по умолчанию, но не повод отправлять больше, чем нужно.