Привет, Хабр!

TLS‑блок в конфиге nginx — самая копируемая часть всей конфигурации. Его берут из генератора Mozilla, из статьи с хорошим рейтингом или из соседнего проекта, вставляют один раз и больше не открывают. Работает же. Сертификат валиден, замочек зелёный, SSL Labs рисует букву A.

За последние пару лет в этом блоке устарело сразу несколько вещей, причём по‑разному.

  • Одна директива перестала работать вообще и теперь только пишет предупреждение в лог.

  • Другой популярный совет из тех же гайдов сегодня скорее вредит, чем помогает, потому что nginx научился делать это сам.

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

Заметно ничего не ломается. Сайт открывается, ошибок нет. Понять, что часть конфига превратилась в декорацию, можно, только если пойти и проверить специально.

Разберём, что именно изменилось и как выглядит актуальный вариант.

OCSP stapling: механизма больше нет

Начнём с самого показательного случая.

Идея OCSP была простой. У браузера должен быть способ узнать, не отозван ли сертификат, который ему только что предъявили.

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

Проблема в том, что такой запрос делает браузер, а значит удостоверяющий центр видит, кто на какой сайт заходит.

Плюс это дополнительный сетевой вызов в момент установки соединения, и если сервис отвечает медленно, страдает время открытия страницы.

Stapling придумали как раз для этого. Сервер сам раз в некоторое время ходит к удостоверяющему центру, получает подписанный ответ и прикладывает его к рукопожатию.

Браузер получает подтверждение вместе с сертификатом, никуда не ходит, приватность не страдает. В nginx включается двумя строчками, которые с тех пор кочуют из конфига в конфиг:

ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 valid=300s;

А потом в Let's Encrypt посчитали нагрузку. Их OCSP‑сервис обрабатывал порядка двенадцати миллиардов запросов в сутки, и каждый такой запрос сообщал центру, какой пользователь с какого адреса открывает какой сайт.

В декабре 2024 они объявили, что от OCSP отказываются полностью, в пользу списков отозванных сертификатов.

Даты стоит запомнить, потому что по ним видно, касается это вас или ещё нет:

  • 30 января 2025 — перестали работать запросы с расширением OCSP Must‑Staple.

  • 7 мая 2025 — из новых сертификатов убрали ссылку на OCSP‑сервис, вместо неё туда добавили ссылку на CRL.

  • 6 августа 2025 — OCSP‑сервисы выключены совсем.

Практическое следствие для nginx выглядит так:

[warn] "ssl_stapling" ignored, no OCSP responder URL in the certificate

Директива есть, сертификат есть, а вот ссылок на сервис в сертификате нет, и stapling молча не работает. Никаких ошибок, только предупреждение при проверке конфига, которое обычно никто не читает.

Что с этим делать, зависит от удостоверяющего центра. Обе строчки со stapling для сертификатов Let's Encrypt можно смело убирать: они уже ничего не дают. У коммерческих центров, которые пока продолжают отдавать OCSP, stapling остаётся рабочей оптимизацией. Трогать его незачем.

Отдельно стоит проверить options-ssl-nginx.conf, который кладёт certbot. Во многих установках директивы живут именно там, и человек, правивший свой конфиг, их не находит.

Отзыв заменили сроком жизни

История с OCSP интереснее, чем кажется, потому что за ней стоит смена подхода целиком.

Отзыв сертификатов всегда работал плохо. Списки большие, загружаются не всегда, браузеры относятся к проверке по‑разному, а между компрометацией ключа и тем моментом, когда об этом узнают все клиенты, проходят часы или дни. Вместо того чтобы чинить этот механизм, индустрия решила сделать его ненужным.

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

Let's Encrypt сделал шестидневные сертификаты общедоступными 15 января 2026 года, срок жизни у них ровно 160 часов.

Заодно появились сертификаты на IP‑адреса, но только в коротком варианте, потому что адреса меняются чаще доменов.

Общее направление задаёт не отдельный центр, а правила отрасли: с 15 марта 2029 года максимальный срок жизни сертификата составит 47 дней. Let's Encrypt планирует опустить свой предел до 45 дней к февралю 2028-го, оставаясь пока на девяноста днях по умолчанию.

Для конфигурации nginx отсюда следует одно, зато очень важное.

Обновление сертификата из редкой операции превращается в регулярную. Перезагрузка воркеров каждые несколько дней вместо раза в два месяца.

Пригодится директива, появившаяся в версии 1.27.4:

ssl_certificate_cache max=1000 inactive=20s valid=1m;

Она кэширует загруженные сертификаты и ключи, что имеет смысл в конфигурациях, где путь к сертификату задан переменной и файл читается на ходу.

Возобновление сессии выключено по умолчанию

Теперь к настройке, про которую большинство уверено, что она работает сама.

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

Механизмов два.

  1. Первый, по идентификатору сессии, хранит состояние на сервере. Клиент присылает идентификатор, сервер ищет его в своём кэше и достаёт параметры. Настраивается директивой ssl_session_cache.

  2. Второй хранит состояние у клиента, в тикете. Сервер отдаёт зашифрованный своим ключом блоб, клиент присылает его обратно, сервер расшифровывает и восстанавливает сессию. Ничего хранить не надо, зато всё держится на сохранности ключа шифрования.

Неприятность в том, что первый механизм по умолчанию выключен.

Значение ssl_session_cache по умолчанию — none, и это означает буквально следующее: nginx говорит клиенту, что сессию можно возобновить, а сам её не сохраняет.

Клиент приходит с идентификатором, попадает в пустоту и делает полное рукопожатие заново.

Включается кэш одной строкой:

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;

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

При восьми воркерах шансы попасть на нужный — примерно один к восьми. Общий кэш лежит в разделяемой памяти и виден всем.

Размер считается просто: один мегабайт вмещает около четырёх тысяч сессий, так что десять мегабайт это сорок тысяч.

Время жизни по умолчанию пять минут, что для возвращающихся пользователей мало, отсюда и сутки в примере.

Совет «выключить тикеты» устарел

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

Отсюда и совет: либо ротируйте ключи скриптом через ssl_session_ticket_key, либо выключайте тикеты вовсе.

Начиная с версии 1.23.2 nginx делает это сам.

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

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

Вторая часть проблемы в том, что выключение тикетов сегодня обходится дороже, чем раньше. В TLS 1.3 возобновление устроено на общих секретах, которые передаются как раз тикетами, и старый механизм с идентификаторами там не используется.

Строчка ssl_session_tickets off в конфигурации, где клиенты ходят по TLS 1.3, не просто убирает один способ возобновления — она убирает возобновление целиком.

То есть современный адекватный вариант выглядит наоборот:

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets on;

Ручная ротация через ssl_session_ticket_key остаётся нужной ровно в одном случае: когда за балансировщиком стоит несколько машин с nginx и клиент может попасть на любую из них. Тогда ключ должен быть общим, и раскладывать его придётся самостоятельно.

0-RTT: быстро, но не для всего

Раз уж речь зашла про возобновление, стоит сказать и про его агрессивную версию.

TLS 1.3 разрешает клиенту отправить данные приложения вместе с самым первым пакетом, не дожидаясь завершения рукопожатия. Это экономит целый круг обмена, что на мобильной сети означает десятки, а иногда и сотни миллисекунд.

В nginx включается одной строкой:

ssl_early_data on;

Проблема у механизма одна, зато серьёзная.

Ранние данные не защищены от повтора: злоумышленник, перехвативший этот первый пакет, может отправить его ещё раз, и сервер выполнит запрос повторно.

Для чтения это безобидно, для операции, которая что‑то меняет, — нет.

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

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

server {
    ssl_early_data on;

    location / {
        proxy_pass http://backend;
        proxy_set_header Early-Data $ssl_early_data;
    }

    location /api/payments {
        if ($ssl_early_data = 1) {
            return 425;
        }
        proxy_pass http://backend;
    }
}

Код 425 придуман специально для этого случая и означает «повтори запрос по установленному соединению». Клиенты, поддерживающие 0-RTT, обрабатывают его корректно.

Разбираться с идемпотентностью в приложении некогда? Ну, тогда лучше оставить ssl_early_data выключенным. Выигрыш в один круг обмена того не стоит, если ценой будет повторно проведённый платёж.

Мелочи, которые тоже поменялись

Несколько вещей, о которых старые гайды не знают просто потому, что их тогда не существовало.

Шифры TLS 1.3 не настраиваются директивой ssl_ciphers, она относится только к более старым версиям протокола. Начиная с 1.19.4 для этого есть ssl_conf_command, который передаёт параметр напрямую в OpenSSL:

ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;

Трогать это, впрочем, почти никогда не нужно: набор в TLS 1.3 короткий, и все варианты в нём приемлемые.

  • В 1.27.2 появилась ssl_key_log для отладки шифрованного трафика. Она пишет сессионные ключи в файл в том формате, который понимает Wireshark, и позволяет посмотреть содержимое соединения. Вещь полезная и очень опасная: файл с ключами даёт возможность расшифровать записанный трафик, поэтому на боевом сервере ей не место.

  • И ssl_reject_handshake on — способ аккуратно отклонять соединения к серверному блоку по умолчанию, вместо того чтобы отдавать первый попавшийся сертификат клиенту, который пришёл с незнакомым именем в SNI.

В итоге

Общая мысль во всём этом такая.

TLS‑блок кажется той частью конфига, которую настроил один раз и забыл, потому что криптография меняется медленно.

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

Раз в год туда стоит заглядывать, хотя бы чтобы убедиться, что половина директив всё ещё что‑то значит.

А у вас в конфиге ещё живёт ssl_stapling?

Если TLS‑конфиг давно работает и не вызывает ошибок, легко решить, что возвращаться к нему незачем. Но именно такие настройки со временем превращаются в технический долг: сначала нужно проверить, что действительно работает, затем понять, какие механизмы уже устарели, чтобы поддерживать инфраструктуру предсказуемо и не обнаруживать проблемы уже в production.

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

  • 5 октября в 19:00. «Автоматические TLS‑сертификаты: модуль ACME (Angie)». Записаться

  • 20 октября в 19:00. «Балансировка HTTP и L4 сервисов в Angie (Angie)». Записаться

Больше бесплатных уроков и материалов по инфраструктуре можно найти в этом посте.

Комментарии (1)


  1. ifap
    22.09.2026 11:30

    SSL Labs рисует букву A.

    Он ее рисует для серверов, не поддерживающих ни одного AEAD-шифронабора, так что сама буква ничего не значит.