Привет, Хабр!
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 — это дополнительный обмен пакетами и криптография с открытым ключом. Возобновление сессии позволяет клиенту, который уже был на сайте, пропустить дорогую часть. Экономия заметная. Особенно на мобильных сетях с большой задержкой.
Механизмов два.
Первый, по идентификатору сессии, хранит состояние на сервере. Клиент присылает идентификатор, сервер ищет его в своём кэше и достаёт параметры. Настраивается директивой
ssl_session_cache.Второй хранит состояние у клиента, в тикете. Сервер отдаёт зашифрованный своим ключом блоб, клиент присылает его обратно, сервер расшифровывает и восстанавливает сессию. Ничего хранить не надо, зато всё держится на сохранности ключа шифрования.
Неприятность в том, что первый механизм по умолчанию выключен.
Значение 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)». Записаться
Больше бесплатных уроков и материалов по инфраструктуре можно найти в этом посте.
ifap
Он ее рисует для серверов, не поддерживающих ни одного AEAD-шифронабора, так что сама буква ничего не значит.