---
title: "Оповещения об истечении SSL-сертификата: писем больше не будет"
description: "Let's Encrypt больше не шлёт письма об истечении, а сертификаты живут 200 дней. Проверенный скрипт openssl, верные пороги и оповещения, которые дойдут."
date: 2026-09-18
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - истечение SSL-сертификата
  - мониторинг TLS
  - certbot
  - оповещения о падении сервера
  - оповещения звонком
---

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

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

**Первое: страховочная сетка убрана.** Let's Encrypt закрыл сервис уведомлений об истечении **4 июня 2025 года** — те самые письма, которые тихо спасали тысячи сайтов, когда ломалась автоматика. Обоснование было разумным (большинство подписчиков продлевает автоматически, хранить миллионы адресов — это риск для приватности, а сервис стоил «десятки тысяч долларов в год»), и заканчивается объявление советом найти сторонний мониторинг ([Let's Encrypt](https://letsencrypt.org/2025/06/26/expiration-notification-service-has-ended)). Многие прочли, согласились — и вторую половину так и не сделали.

**Второе: запас прочности схлопнулся.** С **15 марта 2026 года** публичный TLS-сертификат действует максимум 200 дней, с 15 марта 2027 года — 100 дней, с 15 марта 2029 года — 47 дней, согласно [голосованию SC-081v3 в CA/Browser Forum](https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/). Let's Encrypt движется быстрее потолка: шестидневные сертификаты стали общедоступны 15 января 2026 года, а план переводит профиль по умолчанию на 64 дня в феврале 2027-го и на 45 дней в феврале 2028-го ([Let's Encrypt](https://letsencrypt.org/2025/12/02/from-90-to-45)).

Оба изменения толкают в одну сторону. Продление происходит чаще — значит, у него больше шансов сломаться, а когда оно ломается, писем больше никто не шлёт. Это руководство — та самая проверка, которая закрывает разрыв: протестированный shell-скрипт, пороги, осмысленные для коротких сертификатов, и способ сделать так, чтобы последний случай звонил на телефон через [Echobell](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-ssl-certificate-expiry-alerts-ru&mt=8).

## Насколько на самом деле серьёзен сбой из-за сертификата?

**Настолько, что более трети организаций столкнулись с ним за последний год.** В отчёте DigiCert «2026 Global Certificate Management Outlook» — опрос Propeller Insights среди 1001 руководителя в области ИТ и кибербезопасности в США, Великобритании и Австралии, проведённый в мае 2026 года — **более трети** организаций сообщили о простое сервиса из-за истёкшего сертификата за прошедший год. Почти **три четверти** набрали не менее пяти часов простоя, связанного с сертификатами, **каждая пятая** — 25 часов и более, а почти **каждая четвёртая** заявила, что самый серьёзный инцидент обошёлся более чем в **250 000 долларов** ([DigiCert](https://www.globenewswire.com/news-release/2026/09/09/3358724/0/en/digicert-research-finds-certificate-failures-are-a-six-figure-infrastructure-risk.html)).

Самое интересное в этих цифрах — длительность. Пять часов — это не то, сколько занимает продление; продление занимает секунды. Пять часов — это сколько ушло у кого-то на то, чтобы *узнать*.

## Почему автоматического продления недостаточно?

**Потому что автоматика продления падает молча, а переставший запускаться 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](https://letsencrypt.org/2025/12/02/from-90-to-45)).

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

## Как проверить срок сертификата из командной строки?

**Один конвейер `openssl`, без зависимостей.** Для живого хоста:

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -startdate -enddate
```

Для файла на диске:

```bash
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -enddate
```

Флаг `-servername` на общем IP не опционален: без SNI вы получите сертификат, который сервер считает своим по умолчанию, а это может быть не тот, о котором вы беспокоитесь.

Если нужен ответ «да/нет», разбор дат можно пропустить полностью. `openssl x509 -checkend <секунды>` завершается с кодом **0**, если сертификат переживёт это окно, и **1**, если истекает внутри него (включая уже истёкший):

```bash
openssl x509 -in fullchain.pem -noout -checkend $((14 * 86400)) \
  || echo "истекает менее чем через 14 дней"
```

Этот контракт кодов возврата и есть весь примитив мониторинга. Всё дальнейшее — лишь обвязка вокруг него.

### Как поймать случай «продлили, но не перезагрузили»

**Сравните то, что на диске, с тем, что реально отдаётся.** Это проверка, которую почти никто не делает, и именно она ловит сбой, невидимый для самой автоматики продления:

```bash
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](https://letsencrypt.org/2025/09/16/ari-rfc) (ARI, опубликован как RFC 9773), где удостоверяющий центр сам сообщает клиенту, когда продлевать, и оповещайте только о повторяющихся сбоях клиента.

Вычисление доли — четыре строки, и оно делает один и тот же скрипт корректным для всех ваших сертификатов независимо от издателя:

```bash
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](/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)). Для сертификатов это важно, потому что последняя ступень таблицы выше — как раз та, на которую вы наступите в три часа ночи в воскресенье.

### Шаг 1 — Создайте два канала, а не один

Создайте канал в приложении, задайте тип уведомления **Срочное** и назовите его «Сертификаты истекают». Создайте второй с типом **Звонок** и названием «Сертификат вот-вот истечёт». Скопируйте URL вебхука из деталей каждого канала; он выглядит как `https://hook.echobell.one/t/<channel-token>`. Относитесь к ним как к секретам: тот, у кого есть URL звонкового канала, может позвонить вам ([руководство по вебхукам](/docs/webhook)).

Составьте шаблоны так, чтобы по уведомлению можно было действовать прямо с заблокированного экрана:

```
Заголовок: TLS {{state}}: {{host}}
Текст: осталось {{daysLeft}} дн., истекает {{notAfter}} — выдан {{issuer}}
```

Любой ключ JSON, который вы отправите, становится переменной ([шаблоны](/docs/template)).

### Шаг 2 — Запустите проверку

Это скрипт из разделов выше, собранный воедино и протестированный от начала до конца. Сохраните его как `/usr/local/bin/cert-watch`:

```bash
#!/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`, поэтому не-веб сертификаты тоже покрыты:

```bash
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:

```ini
# /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
```

```ini
# /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 заставит сломавшееся продление заявить о себе:

```ini
# /etc/systemd/system/certbot.service.d/echobell.conf
[Unit]
OnFailure=echobell-alert@%n.service
```

```ini
# /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` — три строки:

```bash
#!/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 — Разделите лестницу условиями

Оба канала получают одну и ту же полезную нагрузку; какой из них действительно сработает, решают [условия](/docs/conditions). На срочном канале:

```
state == "expiring" && daysLeft > 3
```

На звонковом канале:

```
state == "expired" || state == "unreachable" || state == "renewal-failed" || daysLeft <= 3
```

Обратите внимание: `<=` приводит обе стороны через `Number()`, поэтому `daysLeft`, отправленный строкой, всё равно сравнивается численно. В условиях нет оператора «содержит» — именно поэтому скрипт шлёт явное поле `state`, а не свободный текст, который пришлось бы разбирать по шаблону.

### Шаг 5 — Проверьте, прежде чем доверять

Направьте скрипт на хост с заведомо плохим сертификатом и посмотрите, придёт ли оповещение:

```bash
ECHOBELL_CERT_HOOK="https://hook.echobell.one/t/<token>" cert-watch expired.badssl.com
```

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

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

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

- В **Uptime Kuma** есть встроенное уведомление об истечении сертификата — направьте его на канал ([руководство по Uptime Kuma](/docs/developer/uptime-kuma), [настройка звонков](/blog/uptime-kuma-phone-call-alerts)).
- **Prometheus + Alertmanager** с blackbox exporter дают `probe_ssl_earliest_cert_expiry`; повесьте на него правило и маршрутизируйте в канал ([руководство по Prometheus](/docs/developer/prometheus), [звонки через Alertmanager](/blog/alertmanager-phone-call-alerts)).
- Правила оповещений **Grafana** шлют POST прямо в канал ([руководство по Grafana](/docs/developer/grafana)).
- **Upptime** и **UptimeRobot** закрывают публичные эндпоинты ([Upptime](/docs/developer/upptime), [UptimeRobot](/docs/developer/uptimerobot)).
- **Всё, что умеет только письма** — портал самого УЦ, уведомления ACM у облачного провайдера, внутренний PKI — решается правилом пересылки. У каждого канала свой адрес, а `from`, `to`, `subject`, `text` и `html` доступны как переменные ([почтовые триггеры](/docs/email-trigger)).

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

## Чего Echobell не делает

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

**Echobell делает:** превращает вебхук или письмо в обычный пуш, срочное уведомление или звонок; фильтрует условиями; форматирует шаблонами; доставляет один триггер каждому подписчику общего канала, и каждый сам выбирает срочность.

**Echobell не делает:**

- **Не обнаруживает и не инвентаризует ваши сертификаты.** Он не сканирует сеть, не обходит логи прозрачности сертификатов и не расскажет про сертификат, о выпуске которого никто не помнит. Скрипт выше проверяет только перечисленные вами хосты. Разрастание сертификатов — реальная проблема, и это не её решение.
- **Ничего не продлевает.** У него нет ACME-клиента и доступа к вашим ключам. Он сообщает, что продление сломалось; чинить по-прежнему вам.
- **Не проверяет сертификаты по собственному расписанию.** Размещённых проб нет. Смотреть должно что-то, что запускаете вы: таймер, задача CI, ваш существующий мониторинг.
- **Не даёт дежурных ротаций, политик эскалации и подтверждений.** Нет «если за пять минут никто не ответил, звони следующему». Если это нужно, вам нужна платформа управления инцидентами — см. [сравнение альтернатив Opsgenie](/blog/opsgenie-end-of-life-alternatives).
- **Не гарантирует доставку.** Звонок зависит от инфраструктуры пушей, сети и заряженного телефона. Он сокращает расстояние между поломкой и осознанием, но это не мера контроля, на которую можно опереться абсолютно.

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

### 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, положите его на отдельный обычный канал и никогда — на тот, что звонит. Общая версия этого рассуждения — в [борьбе с усталостью от оповещений](/blog/fix-alert-fatigue-developer-guide).

### Может ли вся команда получать оповещение о сертификате?

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

### Работает ли это на macOS?

Работает, с одной оговоркой: BSD-овский `date` не понимает `-d`, поэтому `days_left` и `to_epoch` сначала пробуют форму GNU, а затем откатываются к `date -u -j -f`. `openssl` в macOS по умолчанию — LibreSSL, и `-checkend` с `-fingerprint` ведут себя одинаково. Если поставите OpenSSL из Homebrew, ничего не изменится.

### Безопасно ли отправлять данные о сертификате в уведомлении?

Используемые здесь поля — имя хоста, дата истечения, издатель — публичны; любой может прочесть их с вашего сервера той же командой `openssl`. Не расширяйте нагрузку приватными ключами, внутренними путями или чем-либо из сертификата на непубличном хосте сверх имени хоста. Echobell хранит содержимое и историю уведомлений только на вашем устройстве, оставляя на сервере лишь учётные записи, каналы и подписки ([модель приватности](/docs/features)) — это хорошее значение по умолчанию, но не повод отправлять больше, чем нужно.

---

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

- [Звонки при недоступности API](/blog/phone-call-alerts-api-downtime)
- [Звонки от Uptime Kuma](/blog/uptime-kuma-phone-call-alerts)
- [Оповещения о падении cron-задач](/blog/cron-job-failure-alerts)
- [Как победить усталость от оповещений: руководство для разработчиков](/blog/fix-alert-fatigue-developer-guide)
- [Как критичные оповещения обходят режим фокусирования в iOS](/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)
- [Руководство по интеграции вебхуков](/docs/webhook)
- [Руководство по условиям](/docs/conditions)
