MAATRIX / Блог / OCSP-ответчик у центра сертификации лёг, и сайт открывался 9 секунд

OCSP-ответчик у центра сертификации лёг, и сайт открывался 9 секунд

MAATRIX

Сайт вроде бы работал: бэкенд отвечал быстро, база не тормозила, CPU и память были в норме. Но часть посетителей жаловалась, что страница «висит» секунд девять перед тем, как вообще что-то появится на экране. Разгадка нашлась не в приложении и не на сервере, а в цепочке доверия TLS — конкретно в OCSP-ответчике удостоверяющего центра, который в тот день еле дышал.

Что сломалось

Жалобы начали поступать точечно: не «сайт лежит», а «сайт долго грузится» — и не у всех. Метрики хостинга и мониторинг самого сервера ничего не показывали:

  • CPU держался на 8-12%, память — в пределах обычного;
  • запросы к бэкенду (логи приложения) отвечали за 40-80 мс;
  • база данных не показывала долгих запросов в pg_stat_activity;
  • CDN и балансировщик перед сервером отчитывались о доступности 100%.

При этом синтетический мониторинг доступности (простой HTTP-чекер, который дергает / раз в минуту) периодически фиксировал ответы за 8-10 секунд — и это происходило волнами, без явной привязки к времени суток или нагрузке. Обычный «медленный бэкенд» так себя не ведёт: если тормозит сервер, тормозят все запросы подряд, а не через раз с разбросом.

Первая зацепка появилась, когда сравнили время до первого байта (TTFB) с временем самого TLS-рукопожатия. Отдельно измерили это через:

curl -w '
dns: %{time_namelookup}
connect: %{time_connect}
tls: %{time_appconnect}
ttfb: %{time_starttransfer}
total: %{time_total}
' -o /dev/null -s https://example.com/

В «медленных» замерах почти всё время уходило именно между connect и tls — то есть на этапе установления TLS-соединения, ещё до того, как запрос вообще долетал до nginx и бэкенда. Это сразу сузило круг подозреваемых: дело не в приложении, а где-то на уровне сертификата и обвязки вокруг него.

Гипотезы, которые не подтвердились

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

Версия 1: сертификат истёк или скоро истечёт. Проверили сразу:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

Срок действия был в порядке, до истечения оставалось больше двух месяцев. Эту версию закрыли в первые пять минут.

Версия 2: перегружен сам сервер или упёрлись в лимиты соединений. Проверили ss -s, число открытых файловых дескрипторов nginx, очередь backlog на слушающем сокете — везде штатные значения, никаких признаков исчерпания ресурсов.

Версия 3: проблема на стороне DNS-резолвинга у клиентов. Похоже по симптомам (задержки «где-то в сети»), но time_namelookup в замерах был стабильно низким — DNS отрабатывал быстро что у нас, что у внешних чекеров из разных регионов.

Версия 4: медленный TCP handshake из-за проблем с сетью или MTU. Проверили time_connect отдельно от time_appconnect — TCP-соединение устанавливалось быстро, а вот именно шаг TLS (уже поверх установленного TCP) занимал секунды. Это отличие и стало ключевым — оно указывало конкретно на процесс проверки сертификата, а не на сеть как таковую.

Ни одна из «стандартных» причин не подтвердилась. Пришлось разбираться на уровне самого TLS-рукопожатия — про то, что вообще происходит на этом этапе, подробно разобрано в статье про TLS-рукопожатие и путь до первого байта.

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

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

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

Что показали логи и трафик

Дальше подключили tcpdump на сервере и параллельно сняли трафик с одной из «медленных» машин клиента (через VPN до тестового стенда, чтобы не гонять реальный прод):

tcpdump -i any -w handshake.pcap port 443

Открыли .pcap в Wireshark и отфильтровали по tls.handshake — и увидели, что клиент (в данном случае — сетевой стек одной из версий Windows, где включена проверка отзыва сертификатов по умолчанию) после получения сертификата от сервера инициирует отдельный HTTP-запрос на URL из поля Authority Information Access (AIA) сертификата — тот самый OCSP-запрос к удостоверяющему центру.

Проверили, что за адрес там прописан:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -ocsp_uri

Получили http://ocsp.example-ca.com. Дальше — прямой тест ответчика:

time curl -s -o /dev/null http://ocsp.example-ca.com

И вот тут стало интересно: в обычное время ответ приходил за десятки миллисекунд, а в «плохие» минуты — растягивался на несколько секунд, а иногда запрос вовсе повисал до таймаута клиента. То есть проблема была не у нас на сервере и не в нашей сети — а на стороне инфраструктуры OCSP-ответчика конкретного удостоверяющего центра, который в тот период был перегружен или частично недоступен со стороны той сети, откуда шли запросы.

Отдельно проверили статус OCSP stapling на самом nginx:

echo | openssl s_client -connect example.com:443 -servername example.com -status 2>/dev/null | grep -A5 "OCSP Response"

Ответ был красноречив: OCSP Response Status: no response sent. Stapling формально был включён в конфиге, но фактически не работал — а значит, каждый клиент, который сам проверяет отзыв сертификата (не полагаясь на staple от сервера), был вынужден стучаться в OCSP-ответчик CA напрямую, вместо того чтобы получить готовый подписанный ответ прямо в TLS-рукопожатии.

Реальная причина: пустой staple плюс перегруженный ответчик CA

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

Фактор 1 — OCSP stapling был включён, но не работал. В конфиге nginx стояло:

ssl_stapling on;
ssl_stapling_verify on;

Но не было прописано ни ssl_trusted_certificate (nginx не мог собрать и проверить цепочку до корневого CA, чтобы валидировать OCSP-ответ), ни явного resolver — а без резолвера nginx не может сам сходить в сеть за OCSP-ответом по адресу из AIA, если тот задан доменным именем. В логах nginx при внимательном разборе (уровень warn) находилась строка о том, что stapling не может быть активирован из-за отсутствия сертификата CA в цепочке доверия. Раньше на это никто не обращал внимания, потому что сайт «и так работал» — просто без stapling клиенты, которые проверяют отзыв, делают это сами, в фоне, и в большинстве случаев это остаётся незаметным.

Фактор 2 — в тот день OCSP-ответчик самого CA отвечал медленно. Это внешний фактор, на который повлиять нельзя: у любого удостоверяющего центра OCSP-инфраструктура — это отдельный сервис, который тоже может деградировать под нагрузкой или во время внутренних работ. Пока stapling не работает, каждый клиент, который делает онлайн-проверку отзыва, зависит от скорости и доступности именно этого стороннего сервиса.

Ключевой нюанс: не все клиенты одинаково относятся к недоступности OCSP. Разброс поведения — не баг, а разная политика проверки отзыва у разных стеков:

Клиент / стекПоведение при недоступном OCSP
Большинство современных десктопных браузеровSoft-fail: если ответ не пришёл вовремя, считают сертификат валидным и продолжают, но тратят время на попытку
Корпоративные среды с включённой строгой проверкой отзыва (hard-fail)Ждут ответ до таймаута и могут блокировать соединение
.NET / Java-клиенты с явно включённой проверкой отзыва (revocation checking)Ждут ответ CA синхронно в процессе валидации цепочки, таймаут — несколько секунд
Мобильные ОС и часть современных браузеров с включённым OCSP stapling-onlyНе делают собственный OCSP-запрos вовсе, если сервер не прислал staple — просто пропускают проверку отзыва

Именно из-за этой разницы жалобы были «точечными»: часть аудитории (обычные браузеры на десктопах и корпоративные машины с более строгими настройками безопасности) реально ждала ответа от стороннего OCSP-сервиса, а часть — либо не делала проверку вовсе, либо укладывалась в собственный короткий таймаут и просто теряла время незначительно. Наблюдаемые 9 секунд были близки к суммарному таймауту нескольких попыток соединения с деградировавшим ответчиком CA у самых «дотошных» клиентов — у части пользователей задержка была короче, у части почти незаметна.

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

Почему это не увидели раньше

Отдельный вопрос — почему сломанный stapling молчал месяцами и никто этого не заметил. Причины типичные:

  • Отсутствие мониторинга самого stapling. Мониторили срок действия сертификата (когда истекает), но не проверяли, действительно ли сервер присылает OCSP staple в рукопожатии — это разные вещи.
  • Ложное ощущение «раз сайт открывается — TLS настроен верно». Без stapling сайт действительно продолжает работать: браузеры по умолчанию в основном soft-fail, поэтому деградация была незаметна, пока OCSP-инфраструктура CA работала быстро.
  • Конфиг скопировали из шаблона. ssl_stapling on; добавили по гайду «для галочки», не проверив после этого реальный статус ответа через openssl s_client -status. Это частая ошибка: включить директиву — не то же самое, что убедиться, что она реально работает.

Как починили и как теперь проверяем

Первым делом привели конфиг nginx в рабочее состояние:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;
}

Важные моменты:

  • ssl_trusted_certificate должен указывать именно на цепочку промежуточных сертификатов (chain.pem, без листового сертификата) — иначе nginx не сможет проверить подпись под OCSP-ответом;
  • resolver обязателен, если nginx сам ходит за OCSP-ответом по имени хоста — без него stapling тихо не работает, и в логах при этом не всегда есть явная ошибка на уровне error, только warn или notice;
  • resolver_timeout стоит держать разумным (несколько секунд), чтобы даже при проблемах с самим OCSP-ответчиком CA nginx не подвешивал воркер надолго — при недоступности staple nginx просто отправляет TLS-рукопожатие без него, а не блокирует соединение с клиентом.

После перезагрузки конфига (nginx -s reload) проверили результат тем же способом, которым нашли проблему:

echo | openssl s_client -connect example.com:443 -servername example.com -status 2>/dev/null | grep -A5 "OCSP Response"

Теперь в ответе — OCSP Response Status: successful, и клиенту больше не нужно самому стучаться к CA: staple уже лежит в TLS-рукопожатии, подписанный ответчиком и с ограниченным сроком действия (обычно от нескольких часов до пары суток в зависимости от CA — конкретное время указано в самом OCSP-ответе).

Дальше — то, чего не хватало с самого начала: мониторинг самого факта наличия валидного staple, а не только срока действия сертификата. Простейший вариант — периодическая проверка тем же openssl s_client -status из cron с алертом, если строка OCSP Response Status: successful не найдена:

#!/bin/bash
HOST="example.com"
RESULT=$(echo | openssl s_client -connect "$HOST:443" -servername "$HOST" -status 2>/dev/null | grep "OCSP Response Status")

if [[ "$RESULT" != *"successful"* ]]; then
    echo "OCSP stapling не работает на $HOST: $RESULT"
    # здесь — отправка в Telegram/Slack/систему алертинга
fi

Такую проверку удобно повесить рядом с уже существующим контролем сроков действия сертификатов — подробнее о том, как организовать мониторинг сертификатов и доменов в целом, есть отдельный разбор: мониторинг сертификатов и доменов. Дополнительно можно завести проверку через внешние сервисы вроде SSL Labs (тест Test SSL Server показывает статус stapling в отчёте отдельной строкой) — это не заменяет собственный мониторинг, но полезно как второй независимый источник данных, особенно если nginx стоит за балансировщиком или CDN, которые сами терминируют TLS.

Что изменили в процессах после инцидента

Помимо самого фикса конфига, по итогам разбора закрепили несколько правил:

  • в чек-лист выката нового сервера или сертификата добавили обязательный пункт «проверить OCSP stapling через openssl s_client -status», а не полагаться на факт, что директива присутствует в конфиге;
  • в мониторинг добавили отдельный алерт именно на отсутствие staple, независимо от алерта на срок действия сертификата — это разные отказы с разными причинами;
  • задокументировали, какой именно OCSP-адрес (AIA URL) использует текущий CA, чтобы при повторном инциденте сразу проверять доступность конкретно этого хоста, а не гадать;
  • договорились не включать в конфиге директивы «на всякий случай» без проверки, что они реально дают эффект — это касается не только stapling, любая TLS-настройка должна быть проверена инструментально, а не на слово документации.

Отдельно обсудили и приняли как факт: зависимость от внешнего OCSP-ответчика CA — это единая точка отказа, на которую нельзя повлиять напрямую. Единственный способ защититься от её деградации — не давать клиентам вообще заходить к ней напрямую, то есть держать stapling живым и постоянно проверяемым. Если бы staple был валиден с самого начала, деградация ответчика CA прошла бы для сайта совершенно незаметно.

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

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

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

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

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

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

Что такое OCSP stapling простыми словами?

Это когда сервер сам заранее получает от CA подписанный ответ о том, что сертификат не отозван, и прикладывает его к TLS-рукопожатию. Клиенту не нужно самому идти к CA за этой информацией — он получает её сразу от сервера.

Почему не все посетители жаловались на задержку одновременно?

Разные браузеры и ОС по-разному реагируют на недоступность OCSP: часть делает мягкую проверку (soft-fail) и почти не теряет время, часть — строгую (hard-fail) и реально ждёт ответа от CA до таймаута. Отсюда и разброс жалоб.

Может ли ssl_stapling on; в конфиге не работать, даже если ошибок нет?

Да, это как раз и произошло в разобранном случае: без ssl_trusted_certificate и resolver nginx не может получить и проверить OCSP-ответ, при этом явной ошибки уровня error может не быть — только предупреждение в логах.

Как понять, что stapling реально работает, а не просто включён в конфиге?

Единственный надёжный способ — проверить фактический ответ TLS-рукопожатия командой openssl s_client -connect host:443 -status, а не смотреть в конфигурационный файл.

Стоит ли переживать из-за зависимости от OCSP-ответчика конкретного CA?

Стоит держать stapling рабочим и мониторимым именно поэтому — это снимает зависимость сайта от скорости стороннего сервиса при каждом новом соединении.

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

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

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