Оповещения об истечении SSL-сертификата: писем больше не будет

Let's Encrypt больше не шлёт письма об истечении, а сертификаты живут 200 дней. Проверенный скрипт openssl, верные пороги и оповещения, которые дойдут.

Обновлено

Содержание

Под чужой и вашей инфраструктурой сертификатов изменились две вещи, и большинство команд не подстроилось ни под одну.

Первое: страховочная сетка убрана. 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_HOOKURL вебхука вашего канала}"
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

Делайте это с включённым «Не беспокоить» на том телефоне, который действительно будет принимать, и включите в приложении Повтор неудавшегося звонка, чтобы подавленный фокусированием вызов был набран снова. Путь эскалации, который вы ни разу не запускали, — это догадка.

А если мой мониторинг уже проверяет сертификаты?

Тогда подключите его существующий вебхук к каналу и пропустите скрипт. Большинство систем мониторинга уже знают дату истечения; чего им обычно не хватает — это пути, который переживёт спящего человека.

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

Чего 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 хранит содержимое и историю уведомлений только на вашем устройстве, оставляя на сервере лишь учётные записи, каналы и подписки (модель приватности) — это хорошее значение по умолчанию, но не повод отправлять больше, чем нужно.


Читайте также

Похожие статьи

Оповещения для ночной торговли: 6 декабря рынок перестаёт закрываться

6 декабря 2026 года американские акции переходят на 23-часовой торговый день, а криптодеривативы CME торгуются круглосуточно с мая. Разбираем, как направить ночное ценовое оповещение в телефонный звонок через Echobell — и чего более громкое оповещение всё равно не исправит.

Читать далее

Ваш ИИ-агент ждёт вас: превращаем запросы на подтверждение в телефонные звонки

Автономные агенты молча останавливаются и ждут, когда им нужен человек. Ничто в стеке агентов не заставит ваш телефон зазвонить. Разбираем, как связать запросы на подтверждение и сорвавшиеся запуски с настоящим звонком через Echobell.

Читать далее

Оповещения о ликвидации в крипте: звонок на телефон до того, как позиция будет уничтожена

Тихие push-уведомления не спасают при маржин-колле. Разбираем, как направить оповещение о риске ликвидации из TradingView или бота мониторинга в телефонный звонок через Echobell — чтобы падающий коэффициент маржи действительно вас разбудил.

Читать далее