kai.kzLab

Истёк SSL-сертификат: как проверить срок через openssl, починить цепочку и настроить автопродление

Команда KAI Lab · · 8 мин чтения

Если истёк SSL-сертификат, сначала проверьте, какой сертификат на самом деле отдаёт сервер: echo | openssl s_client -connect host:443 -servername host 2>/dev/null | openssl x509 -noout -enddate покажет дату notAfter. Потом выясните, почему не сработало продление: нет таймера certbot, продление падает с ошибкой или новый сертификат выпущен, а веб-сервер его не перечитал, — и чините причину, а не только сертификат.

Снаружи это выглядит по-разному. Браузер показывает NET::ERR_CERT_DATE_INVALID, curl пишет curl: (60) SSL certificate problem: certificate has expired, мобильное приложение просто перестаёт ходить в API. Если на домене включён HSTS, пользователь не сможет даже нажать «всё равно перейти»: Let's Encrypt в руководстве по интеграции прямо предупреждает, что HSTS превращает ошибки сертификата в жёсткий отказ.

Симптом → команда → что смотреть

СимптомКомандаЧто смотреть
Браузер ругается на сертификатopenssl s_client и openssl x509 -noout -datesnotAfter у сертификата, который реально отдаёт сервер
Не тот домен в сертификатеopenssl x509 -noout -ext subjectAltNameесть ли нужное имя в списке DNS:
Сертификат на диске свежий, а отдаётся старыйopenssl x509 -in fullchain.pem -noout -enddateсравнить дату в файле с датой на сервере
unable to get local issuer certificateopenssl s_client -showcerts ...отдаёт ли сервер промежуточный сертификат
Автопродление не сработалоsystemctl list-timersесть ли certbot.timer или snap.certbot.renew.timer
Продление падаетsudo certbot renew --dry-runтекст ошибки и лог в /var/log/letsencrypt/
Нужна проверка в скрипте или мониторингеopenssl x509 -noout -checkend 1209600код выхода 1 — истечёт в ближайшие 14 дней

Шаг 1. Какой сертификат отдаёт сервер

$ echo | openssl s_client -connect shop.example.com:443 -servername shop.example.com 2>/dev/null \
    | openssl x509 -noout -dates -ext subjectAltName
notBefore=Jun 26 03:12:40 2026 GMT
notAfter=Sep 24 03:12:39 2026 GMT
X509v3 Subject Alternative Name: 
    DNS:shop.example.com, DNS:www.shop.example.com

Сертификат выпущен на 90 дней и истёк 24 сентября в 03:12 по UTC. echo | закрывает соединение сразу после рукопожатия, иначе s_client будет ждать ввода. Флаг -servername обязателен: он передаёт имя сайта в TLS (SNI). Без него сервер с несколькими сайтами может отдать сертификат по умолчанию, и вы будете чинить не тот.

Если сервер недоступен снаружи, та же проверка работает с сервера на localhost:443 с тем же -servername. А если имя резолвится не туда, куда вы думаете, сначала разберитесь с DNS — порядок в статье DNS не резолвит.

Что покажет s_client без фильтрации, удобно смотреть на публичных тестовых стендах badssl.com:

$ echo | openssl s_client -connect expired.badssl.com:443 -servername expired.badssl.com 2>&1 | grep -E 'verify error|Verify return code'
verify error:num=10:certificate has expired
    Verify return code: 10 (certificate has expired)

Шаг 2. Сертификат продлён, а отдаётся старый

Частый случай: certbot давно получил новый сертификат, а сайт всё равно отдаёт старый. Сравните файл на диске с тем, что видно снаружи:

$ sudo openssl x509 -in /etc/letsencrypt/live/shop.example.com/fullchain.pem -noout -enddate
notAfter=Nov 22 01:40:12 2026 GMT

На диске сертификат до 22 ноября — его получили месяц назад. Но nginx прочитал файл при запуске и с тех пор держит в памяти старую версию. Лечится перечитыванием конфигурации:

$ sudo nginx -t && sudo systemctl reload nginx

Чтобы это не повторилось, нужен deploy-хук. Если сертификат получали через certbot certonly (webroot, standalone, DNS), certbot только кладёт новые файлы, а перечитать их веб-сервер должен сам. Исполняемые файлы из /etc/letsencrypt/renewal-hooks/deploy/ certbot запускает после каждого успешного продления:

$ sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload nginx
EOF
$ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Та же история, если сертификат скопировали руками: в Docker-том, на балансировщик, на соседний сервер. certbot обновит /etc/letsencrypt/live/, а копия останется старой. Документация certbot поэтому советует не копировать файлы, а указывать в конфигурации сервера путь к ним напрямую.

Шаг 3. Цепочка: unable to get local issuer certificate

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

$ echo | openssl s_client -connect incomplete-chain.badssl.com:443 -servername incomplete-chain.badssl.com 2>&1 | grep -E 'verify error|Verify return code'
verify error:num=20:unable to get local issuer certificate
verify error:num=21:unable to verify the first certificate
    Verify return code: 21 (unable to verify the first certificate)

Коварство в том, что браузер на вашем ноутбуке может открыть такой сайт без ошибок, а curl, Java-клиент или мобильное приложение — нет. Что сервер отдаёт на самом деле, покажет -showcerts: у правильно настроенного сервера после строки 0 s: с вашим доменом идёт 1 s: с промежуточным сертификатом.

Для certbot правило простое: в ssl_certificate у nginx указывается fullchain.pem, в нём сертификат сайта и промежуточные. cert.pem содержит только сертификат сайта — ровно та ошибка, что выше.

Шаг 4. Почему не сработало автопродление

Let's Encrypt выдаёт сертификаты на 90 дней и рекомендует продлевать их каждые 60. Сам certbot начиная с версии 4.0 продлевает сертификат, когда осталась треть срока, а до 4.0 — за 30 дней до окончания. Если сертификат всё-таки истёк, значит, автоматика молчала больше месяца. Проверьте по порядку.

Есть ли вообще расписание. Пакеты certbot ставят таймер systemd или задачу cron. Ищите certbot.timer (пакет из репозитория дистрибутива) или snap.certbot.renew.timer (snap) в systemctl list-timers, а также строку certbot renew в /etc/crontab и /etc/cron.d/. Ничего нет — продлевать некому. Так бывает, если сертификат получили один раз руками, поставили certbot через pip или используют плагин --manual: такие сертификаты, как пишет документация, без хуков автоматически не продлеваются.

Не падает ли продление. Запустите пробный прогон:

$ sudo certbot renew --dry-run

Он проходит все проверки против тестового сервера Let's Encrypt и ничего не меняет. Типичные причины падения: порт 80 закрыт firewall и проверка HTTP-01 не проходит, каталог webroot переехал, DNS-запись домена указывает на другой сервер, у DNS-плагина истёк API-токен. Подробности — в логе /var/log/letsencrypt/letsencrypt.log. Учтите: при --dry-run deploy-хуки не запускаются, если не добавить --run-deploy-hooks.

Доехал ли новый сертификат до сервиса. Это шаг 2: перечитал ли веб-сервер файлы и нет ли копий.

Шаг 5. Мониторинг срока: не надеяться на письма

До лета 2025 года Let's Encrypt присылал письма о скором истечении сертификатов. С 4 июня 2025 года эта рассылка прекращена, и если автоматика тихо сломалась, никто больше не напомнит. Нужен свой мониторинг.

Минимальный вариант — проверка по cron, которая падает, если сертификат истекает в ближайшие 14 дней или сервер вообще не ответил:

#!/bin/bash
host=shop.example.com
if ! echo | openssl s_client -connect "$host:443" -servername "$host" 2>/dev/null \
    | openssl x509 -noout -checkend $((14 * 24 * 3600)) >/dev/null; then
  echo "Сертификат $host истекает меньше чем через 14 дней или сервер недоступен" >&2
  exit 1
fi

-checkend N проверяет, истечёт ли сертификат в ближайшие N секунд, и возвращает 1, если да. Если у вас Prometheus, то же самое делает blackbox_exporter: метрика probe_ssl_earliest_cert_expiry хранит время истечения самого раннего сертификата в цепочке, и алерт на «осталось меньше 14 дней» пишется в одну строку.

Мониторить важно и потому, что сроки сокращаются. Let's Encrypt объявил переход на 45-дневные сертификаты вслед за требованиями CA/Browser Forum. С 10 февраля 2027 года стандартный профиль будет выдавать сертификаты на 64 дня, с 16 февраля 2028 года — на 45. Задача cron «продлевать раз в 60 дней» с такими сроками гарантированно опоздает. Let's Encrypt советует использовать ACME Renewal Information (ARI), когда сервер сам подсказывает клиенту, когда продлевать, — или хотя бы продлевать, когда осталась треть срока.

Сертификаты ломают не только сайты

Самые дорогие истории с сертификатами случались не на веб-сайтах. 6 декабря 2018 года истёк сертификат внутри программного обеспечения узлов SGSN-MME от Ericsson, и без мобильного интернета остались абоненты O2 в Великобритании, SoftBank в Японии и операторов ещё в ряде стран — разбор в статье сбой Ericsson 2018. А 29 февраля 2012 года облако Windows Azure споткнулось о сертификат, у которого дату окончания считали как «та же дата через год» и получили несуществующее 29 февраля 2013 года — сбой Azure 2012.

Вывод для эксплуатации: ведите инвентарь всех сертификатов, а не только публичных. Внутренние сертификаты между сервисами, клиентские сертификаты, сертификаты VPN, Kubernetes и оборудования истекают так же, только мониторят их реже.

В симуляторе DevOps KAI Lab сертификаты появляются уже в первый месяц: на стажёрском дежурстве приходит алерт SSLCertExpiringSoon — платёжному домену осталось три дня, — а в разборах аварий есть история Ericsson. Консоль отвечает реальными выводами openssl, так что проверки из этой статьи можно пройти руками.

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

Как проверить срок действия SSL-сертификата из консоли? echo | openssl s_client -connect домен:443 -servername домен 2>/dev/null | openssl x509 -noout -dates. Для файла на диске — openssl x509 -in файл.pem -noout -enddate.

Сертификат продлён, а сайт отдаёт старый. Почему? Веб-сервер держит сертификат в памяти и не перечитал файлы: нужен reload и deploy-хук certbot. Второй вариант — сертификат отдаёт не этот сервер, а балансировщик, CDN или соседняя машина со старой копией.

Что значит unable to get local issuer certificate? Клиент не может построить цепочку до доверенного корня: обычно сервер не отдаёт промежуточный сертификат. Для certbot проверьте, что в конфигурации указан fullchain.pem, а не cert.pem.

Как часто certbot продлевает сертификаты? Обычно таймер или cron запускают certbot renew дважды в день, а продление происходит, когда сертификату осталось меньше трети срока. Для 90-дневного сертификата это примерно за 30 дней до окончания.

Нужен ли мониторинг, если автопродление настроено? Да. Автопродление ломается молча: закрыли порт 80, сменили DNS, истёк токен. С июня 2025 года Let's Encrypt не присылает писем об истечении, поэтому о проблеме узнают либо из вашего алерта, либо от пользователей.

Источники

Читать дальше