NGINX давно стал стандартом для базовых инфраструктурных задач: терминации HTTPS, раздачи статического контента и балансировки трафика. Однако за 2026 год веб‑сервер совершил технологический рывок в своей основной ветке, который выводит его далеко за рамки классического обратного прокси.
В новых OSS‑версиях появились инструменты, которые раньше требовали покупки коммерческой подписки (NGINX Plus от F5) или развертывания стороннего ПО и сложных связок с Lua. Майский релиз 1.31.0 принес нативную поддержку HTTP Forward Proxy. А сентябрьское обновление 1.31.5 окончательно закрепило этот триумф, добавив полноценный динамический Control API, модуль для нативного извлечения данных из JSON и предикатные location. Параллельно с этим произошли большие изменения в скриптовании: в июне проект njs перешагнул отметку релиза 1.0.0, полностью сменив старый кастомный движок на современный QuickJS.
Давайте подробно разберем главные архитектурные новинки 2026 года. И начнем с самой ожидаемой — маршрутизации на базе предикатов.
Архитектурный переворот: Матрёшки безопасности и инверсия предикатов
Долгое время конфигурация Nginx оставалась строго восходящей: вы обязаны были сначала привязаться к URI в блоке location, а уже внутри него пытаться как‑то разрулить специфику конкретного запроса. Если логика требовала проверять типы методов или кастомные заголовки, инженеры неизбежно скатывались в написание громоздких конструкций с rewrite, генерацией внутренних заголовков или использованием небезызвестного if is evil, который из‑за специфики фаз обработки запросов ломал наследование директив. С выходом Nginx 1.31.5 и появлением предикатных локаций эту устоявшуюся парадигму можно в буквальном смысле развернуть наизнанку. Новая фича поддерживает полноценную вложенность и бесшовно комбинируется с классическим URL‑роутингом, позволяя создавать многоуровневые каскадные фильтры. Теперь вы можете вынести глобальные переменные и проверки на самый верхний уровень, превратив конфигурацию в матрёшку.
Рассмотрим пример, где маршрутизация строится не вокруг структуры эндпоинтов, а отталкивается от степени легитимности трафика:
http { server { # 1. Глобальное разделение по типу запросов location $public_methods { # Доступно всем: например, GET или HEAD запросы proxy_pass http://public_backend; } # 2. Переходим к защищенным методам (POST, PUT, DELETE) location $restricted_methods { # Вложенный URI: проверяем конкретный эндпоинт внутри закрытых методов location /api/v1/private { # Двойной предикатный фильтр внутри URI location $internal_ips { # Доступ разрешен только для доверенных IP-адресов proxy_pass http://secure_backend; } location $bot_traffic { # Ловим ботов, пытающихся делать POST-запросы в приватную зону return 403 "Access Denied for Bots\n"; } } # Другие URI, требующие проверки $restricted_methods location /api { ... } location /wp-admin { ... } } } }
В основе этой конфигурации лежит принцип поэтапного отсечения лишнего. На первом этапе входящий запрос оценивается предикатами верхнего уровня: если это тяжелый POST, Nginx сразу отправляет его в изоляцию блока $restricted_methods. Только внутри этой зоны веб‑сервер начинает сопоставлять текстовый URI запроса с целевым эндпоинтом /api/v1/private. И уже добравшись до самого низа цепочки применяет финальные триггеры безопасности — проверяет, принадлежит ли IP‑адрес внутренней сети ($internal_ips) или перед нами подозрительный парсер ($bot_traffic).
Главный профит такого подхода — экономия ресурсов CPU и RAM. Известно, что вычисление сложных переменных (например, парсинг тела запроса или сверка сигнатур через регулярные выражения) под нагрузкой может просадить производительность. Каскадная структура решает эту проблему: nginx выполняет тяжелые предикаты только тогда, когда запрос уже успешно прошёл все базовые фильтры по методу и URI.
Связка предикатов и NJS
Для реализации нетривиальной логики (например, динамического подсчета метрик или валидации сессий) встроенных возможностей маппинга может не хватить. Раньше интеграция NJS требовала написания громоздкого JS‑кода с внутренними редиректами, что усложняло конфиг. С появлением предикатов NJS выполняет только одну атомарную задачу — вычисляет флаг, а nginx распределяет трафик на основе этого значения в location.
Рассмотрим кейс: нужно на лету анализировать количество символов в теле POST‑запроса и блокировать слишком раздутые запросы на подлете, возвращая 413. Создаем простой скрипт http.js, который проверяет длину payload:
function counter(r) { const words = r.variables['request_body'].trim().split(/\s+/).length; return words > 10 ? 1 : 0; // взводим предикат в 1, если слов > 10 } export default {counter};
Теперь интегрируем JS‑логику в nginx.conf. Директива client_body_early_read принудительно считывает тело запроса на ранней стадии, а js_set связывает результат функции с переменной $many_tokens, которая затем используется как условие для location:
http { client_body_early_read on; # Читаем тело заранее js_import /path/to/conf/http.js; js_engine qjs; js_set $many_tokens http.counter; # Сетим результат JS в переменную server { listen 8085; # Предикатный локация: сработает, если JS вернул 1 location $many_tokens { return 413 "Request body has too many tokens\n"; } # Если вернуло 0, то нам сюда location / { return 200 "Request is small, OK\n"; } } }
При обработке запроса движок QuickJS вычисляет значение переменной. Если на входе длинный текст, функция возвращает 1 — nginx сразу активирует предикатный location и отдает 413. Короткие запросы проваливаются в корень если вернуло 0.
root:~$ curl -X POST -d "a b c d e f g h j k l" 0.0.0.0:8085 Request body has too many tokens root:~$ curl -X POST -d "a b c d" 0.0.0.0:8085 Request is small, OK
Нативный парсинг JSON
Параллельно с логикой условных адресов в осеннем релизе 1.31.5 появилось ещё одно нововведение — официальный модуль ngx_http_json_module, который закрывает одну из главных архитектурных брешей. До этого момента NGINX оставался слеп к телу запроса, если речь шла о структурированных данных: приходилось либо подключать интерпретатор JavaScript, либо разворачивать громоздкие Lua‑скрипты, чтобы вытащить из входящего JSON хотя бы один идентификатор. Новый модуль решает эту задачу на уровне конфигурации, позволяя парсить JSON прямо из коробки. С помощью директивы json_set веб‑сервер может извлечь конкретное значение по заданному ключу из тела запроса и записать его в стандартную переменную.
Разработчики уделили особое внимание производительности: сколько бы переменных вы ни извлекали из одного входящего документа, сам пакет данных парсится строго один раз за запрос и только в момент первого обращения к любой из этих переменных, что минимизирует накладные расходы на память. Чтобы эта связка работала корректно, в архитектуру ядра была добавлена директива client_body_early_read, которая заставляет веб‑сервер считывать тело запроса сразу после обработки заголовков, подготавливая данные для анализа. Внедрение этого модуля снижает задержки, так как первичная валидация и базовая маршрутизация полезной нагрузки теперь происходят на компилируемом коде самого прокси‑сервера, минуя стадию передачи трафика в бэкенд‑приложения.
Единственным важным нюансом остается сборка: по соображениям безопасности и экономии ресурсов модуль не включается по умолчанию, и для его активации необходимо компилировать из исходников со специальным флагом
upstream standard_backend { server 10.0.0.60:8080; } server { listen 80; server_name api.company.ru; # Включаем предварительное чтение тела запроса для анализа JSON client_body_early_read on; # Извлекаем значение из вложенной структуры JSON-документа json_set $user_tier "user.subscription.tier"; location /api/v2/ { # Проверяем извлеченную из JSON переменную через условный адрес location (if $user_tier = "premium") { proxy_pass http://VIP_backend; } # Маршрут по умолчанию для всех остальных уровней подписки proxy_pass http://standard_backend; } }
Всё начинается с предварительного чтения: директива заставляет nginx полностью загрузить тело запроса в память сразу после валидации заголовков, не дожидаясь этапа передачи на upstream. Дальше в дело вступает извлечение данных, которое просто регистрирует новую переменную и указывает точечный путь к нужному полю в JSON.
Парсинг запускается только тогда, когда веб‑сервер доходит до обработки блока путей и натыкается на условный адрес, требующий проверки этой переменной. В этот момент парсер однократно считывает документ из памяти, находит вложенный объект подписки, извлекает уровень пользователя и передает его условию. Если юзер оказывается премиальным, запрос мгновенно улетает на изолированную группу VIP‑серверов. В противном случае управление возвращается в родительский контекст, и трафик направляется на стандартные пути.
Раннее исследование тела запроса
Чтобы парсинг JSON и условные адреса вообще стали возможны, разработчикам пришлось переписать классический конвейер обработки данных внутри веб‑сервера, внедрив механизм раннего исследования тела запроса. Исторически nginx работал как сверхбыстрый почтовый распределитель: он принимал пакет, читал исключительно внешние данные — строку адреса и заголовки — и на основе этой информации выбирал маршрут. Само тело полезной нагрузки на этом этапе оставалось в буфере, и веб‑сервер распечатывал его только после того, как запрос долетал до конкретного целевого блока конфигурации.
Новая директива client_body_early_read меняет эту последовательность, принудительно заставляя веб‑сервер считывать и буферизовать тело входящего сообщения еще до того, как начнется сопоставление путей. Обладая доступом к содержимому пакета на самой ранней стадии, прокси‑сервер может заблокировать некорректный или вредоносный JSON‑файл, даже не пытаясь установить соединение с бэкенд‑приложением.
Однако, важно помнить, что чтение полезной нагрузки — это ресурсоемкая операция, требующая процессорного времени и памяти. Разработчики учли этот риск, поэтому директива поддерживает использование динамических переменных. Вместо того чтобы бездумно включать раннее чтение для абсолютно всего входящего трафика, можно настроить таблицу соответствий на основе заголовка типа контента.
# Проверяем тип контента в заголовках map $http_content_type $is_json { "application/json" 1; default 0; } server { listen 80; server_name api.company.ru; # Включаем раннее чтение тела только для JSON-трафика client_body_early_read $is_json; location /v1/ { # Дальнейшая логика парсинга и условных адресов } }
В таком сценарии nginx будет активировать механизм предварительного анализа полезной нагрузки только тогда, когда в заголовках явно указан JSON, а весь остальной поток данных продолжит обрабатываться по классической схеме.
Сессионная привязка на базе кук
Еще одним сдвигом в экосистеме стало появление официальной поддержки липких сессий непосредственно в OSS, начиная с релизов ветки 1.29.x и стабильной версии 1.30.x. Ранее полноценное закрепление пользователя за конкретным сервером группы без использования сторонних модулей было эксклюзивной возможностью PLUS версии. Инженерам приходилось довольствоваться привязкой по IP‑адресу клиента, которая работала очень интересно, что неизбежно приводило к перекосам в балансировке из‑за сетевых шлюзов крупных провайдеров или мобильных операторов, когда сотни разных пользователей имели один общий адрес.
Появление sticky cookie в блоке групп серверов полностью закрывает эту проблему. При первом обращении nginx самостоятельно генерирует уникальную куку, подписывает её и внедряет в HTTP‑ответ бэкенда. Все последующие запросы от этого браузера будут гарантированно маршрутизироваться на тот же самый узел, обеспечивая сохранение состояния сессии или многоэтапных форм авторизации. Для повышения безопасности разработчики добавили встроенную поддержку шифрования куки на базе секретного ключа и автоматический откат к стандартному алгоритму распределения трафика в случае аварийного отключения целевого сервера.
upstream stateful_backend { server 10.0.0.30:8080; server 10.0.0.40:8080; # Активируем привязку сессий через куку (фича релизов 2026 года) sticky cookie srv_id expires=1h domain=.company.ru path=/; } server { listen 80; server_name api.company.ru; location /app/ { proxy_pass http://stateful_backend; } }
HTTP/1.1 и keep‑alive теперь по умолчанию
Самым масштабным изменением в поведении веб‑сервера за 2026 год произошло в модуле проксирования. С момента своего создания и вплоть до весны 2026 года nginx по умолчанию общался с апстримами по протоколу HTTP/1.0, закрывая TCP‑соединение после каждого чиха. Чтобы включить keep‑alive,приходилось руками прописывать пул и кол‑во соединений.
В стабильной ветке 1.30.0 эта историческая парадигма официально ушла в прошлое: теперь протокол HTTP/1.1 и постоянные соединения к апстримам активированы по умолчанию. Если раньше без явного вмешательства nginx отправлял бэкенду заголовок Connection: close, то теперь логика перевернулась. Начиная с новых версий, дефолтная конфигурация автоматически эквивалентна следующим параметрам:
# В версиях 1.29.7 и старше эти параметры активны для всех апстримов по умолчанию: proxy_http_version 1.1; # Заголовок Connection автоматически удерживается в состоянии keep-alive proxy_set_header Connection ""; # Поддержка до 32 активных сессий, обычно делается х2 от пула серверов в апстриме keepalive 32 local;
Более того, теперь по умолчанию выделяет базовый размер пула соединений (до 32 idle‑сессий на каждый рабочий процесс) для любого объявленного апстрима. Код, который мы годами копировали из проекта в проект (proxy_http_version 1.1; proxy_set_header Connection "";), больше просто не нужен.
Для большинства стандартных стендов это изменение дает мгновенный прирост производительности сразу после обновления. За счет исключения постоянных стадий TCP Handshake и TLS‑рукопожатий мы получаем:
Нагрузка на процессор падает, так как ему больше не нужно генерировать тысячи системных вызовов на открытие/закрытие сокетов каждую секунду.
Исчезает вечная проблема исчерпания локальных портов (ошибки
TIME_WAIT), которая раньше часто приводила к падению высоконагруженных шлюзов.
Проксирование по HTTP/2 и когда оно действительно имеет смысл
Долгое время поддержка HTTP/2 ограничивалась исключительно клиентским контуром: веб‑сервер принимал мультиплексированный поток от браузеров, но в сторону бэкендов трафик неизменно уходил по протоколу HTTP/1.1. В недавних релизах разработчики наконец представили полноценную поддержку HTTP/2 для апстримов (как зашифрованного h2, так и чистого текста h2c), активируемую через долгожданную директиву proxy_http_version 2. Теперь можно пробросить протокол по всей цепочке от пользователя до конечного микросервиса.
upstream grpc_and_h2_services { server 10.0.0.90:50051; server 10.0.0.101:50051; } server { listen 443 ssl; # Включаем HTTP/2 для внешних клиентов http2 on; server_name api.company.ru; location /grpc.service/ { # Включаем проксирование по HTTP/2 в сторону бэкенда proxy_http_version 2; # Передаем трафик на современные h2c-совместимые приложения proxy_pass http://grpc_and_h2_services; } }
Когда включать HTTP/2 на бэкенд жизненно необходимо. Существует лишь несколько сценариев, где переход на proxy_http_version 2 оправдан:
Во‑первых, это сквозной gRPC‑трафик. Поскольку gRPC неразрывно завязан на механизмы HTTP/2 (фреймы, двунаправленные стримы), трансляция его в HTTP/1.1 на балансировщике была технически невозможна. Новая директива позволяет нативно проксировать gRPC‑сервисы без сторонних модулей по типу ngx_http_grpc_module иногда он был слишком избыточен.
Во‑вторых, это интеграция с современными Service Mesh вроде Envoy Gateway, которые изначально спроектированы под h2-коммуникации и требуют сохранения единого протокола по соображениям безопасности и сквозного контроля потока.
В‑третьих, это защита от десинхронизации: использование разных версий протокола на границе и внутри сети порождало опасные уязвимости подмены запросов, а единый HTTP/2 устраняет этот вектор атак.
Как проверить готовность бэкенда к приему трафика h2c?
Прежде чем активировать проксирование по протоколу HTTP/2 в файле конфигурации, необходимо достоверно убедиться, что приложение способно корректно обрабатывать входящий трафик новой версии без шифрования. Сложность такой проверки заключается в том, что все современные браузеры поддерживают этот протокол исключительно поверх TLS, поэтому использовать стандартные веб‑интерфейсы для отладки не получится.
Первый подход, запускаемый командой curl -v --http2 http://localhost:8080, отправляет бэкенду стандартный заголовок обновления протокола, в ответ на который приложение возвращает статус‑код 101 и переключает сессию.
Однако сам nginx при проксировании не тратит время на согласование протоколов и сразу начинает слать данные во фреймах HTTP/2. Для эмуляции этого сценария необходимо использовать команду curl -v --http2-prior-knowledge http://localhost:8080.
Если в ответ на этот вызов приложение падает с ошибкой сетевого соединения или возвращает четырехсотый код некорректного запроса, значит, бэкенд технически не готов к приему сквозного трафика.
Control API
В релизе 1.31.5 произошло еще одно событие: инструмент Control API из коммерческой версии Plus полностью перенесли в OSS. Главная фишка нового интерфейса — синхронная перезагрузка настроек с мгновенным JSON ответом. Теперь при отправке запроса главный процесс сам собирает логи проверки, контролирует права на сокеты и возвращает подробный отчет прямо в ответе.
Для демонстрации запустим NGINX на локальном сокете nginx -l unix:/tmp/nginx.sock, а затем запросим статус рабочих процессов:
curl --unix-socket /tmp/nginx.sock http://localhost/1/control/processes
Curl обращается к эндпоинту /1/control/processes. В ответ отдается JSON со списком рабочих процессов, их PID и потреблением ресурсов. Точно так же через адрес /1/control/config можно выгрузить активные настройки прямо из оперативной памяти сервера (раньше так делать было нельзя) или отправить POST‑запрос для применения нового конфига, сразу получив в ответе код 200 OK либо описание синтаксической ошибки.
Если вы используете Docker, новый флаг нужно передать в качестве аргумента при запуске контейнера, а сам сокет пробросить через volume:
# Запуск контейнера с передачей флага -l и монтированием сокета docker run -d --name nginx-control-api -v /tmp/nginx-sockets:/tmp/nginx-sockets nginx:1.31.5 nginx -g "daemon off;" -l unix:/tmp/nginx-sockets/nginx.sock # Запрос к API из хост-системы curl --unix-socket /tmp/nginx-sockets/nginx.sock http://localhost/1/control/processes
Защита конфиденциальности на этапе рукопожатия: поддержка Encrypted Client Hello
Одним из важнейших нововведений безопасности в ветке 1.29+ и стабильном релизе 1.30.0 стала нативная интеграция расширения Encrypted Client Hello. Эта технология призвана закрыть последнюю фундаментальную уязвимость протокола TLS 1.3 — утечку SNI в открытый текст во время первоначального обмена пакетами. Исторически, любой промежуточный узел, маршрутизатор или провайдер связи мог перехватить стартовый пакет от клиента и точно узнать, какой именно веб‑сайт посещает пользователь, что создавало риски для конфиденциальности данных и позволяло осуществлять точечные блокировки. В отличие от старого и компромиссного протокола ESNI, механизм ECH полностью шифрует всё стартовое сообщение клиента, включая имя сервера и параметры предварительно согласованных ключей.
Для включения этой защиты в конфигурационный блок виртуального сервера добавляется директива ssl_ech_file, указывающая на сгенерированный PEM‑файл с конфигурацией ключей.
server { listen 443 ssl; server_name private-service.company.ru; ssl_certificate /etc/ssl/certs/private.crt; ssl_certificate_key /etc/ssl/private/private.key; # Активируем ECH с помощью конфигурационного файла ключей ssl_ech_file /etc/nginx/ech/company_ech.pem; # Логируем статус шифрования рукопожатий для аудита безопасности access_log /var/log/nginx/ech_access.log combined_with_ech; location / { proxy_pass http://internal_backend; } }
Важный нюанс — это необходимость тесной интеграции с системой доменных имен. Чтобы браузер пользователя узнал о поддержке шифрования и получил публичный ключ сервера до начала TLS‑сессии, администратор должен опубликовать конфигурацию ECHConfig внутри специальной ресурсной записи HTTPS на DNS‑серверах. Без этой сквозной связки браузеры продолжат отправлять классические открытые заголовки. Пример HTTPS записи:
@ IN HTTPS 1 . alpn="h3,h2" ech="Aekm6gA9AHwAAAAAA...AA=="
Где @ — имя хоста, 1 — приоритет записи, . указывает на текущий целевой сервер, alpn перечисляет поддерживаемые HTTP протоколы, а в параметре ech передается сгенерированная веб‑сервером Base64-строка публичного ключа из файла конфигурации.
Конвергенция на транспортном уровне: интеграция MPTCP с HTTP/3 и QUIC
Рассматривая архитектуру высокодоступных и многоканальных систем, инженеры сталкиваются со следующим этапом — нативной интеграцией механизмов многопутевого транспорта с протоколом HTTP/3. Исторически между классическим TCP и современным QUIC шла негласная конкуренция в скорости и надежности доставки данных. Появление поддержки multipath снимает этот барьер, позволяя выстроить гибридную отказоустойчивую сеть. Теперь веб‑сервер может одновременно выступать универсальной точкой входа: удерживать соединение по протоколу HTTP/3 поверх UDP для современных мобильных клиентов и параллельно предоставлять многопутевые маршруты Multipath TCP для клиентов, ограниченных классическим TCP‑стеком. Для реализации такой синергии необходимо убедиться, что используемое ядро Linux инициализировало сетевой стек с поддержкой multipath:
Флаг sysctl net.mptcp.enabled должен возвращать 1. Если возвращается 0, протокол выключен, его можно включить через sysctl -w net.mptcp.enabled=1. Если же ОС возвращает ошибку unknown key, значит текущее ядро не скомпилировано с поддержкой нужной технологии.
server { # 1. Активируем классический HTTPS с поддержкой Multipath TCP # Требует Linux kernel 5.6+ и NGINX 1.29.7+ с включенным MPTCP listen 443 ssl multipath; # 2. Активируем HTTP/3 поверх UDP на том же сетевом порту listen 443 quic reuseport; server_name HA-edge.company.ru; ssl_certificate /etc/ssl/certs/edge.crt; ssl_certificate_key /etc/ssl/private/edge.key; # Явно объявляем поддержку HTTP/3 для клиентских браузеров ssl_protocols TLSv1.3; add_header Alt-Svc 'h3=":443"; ma=86400'; location /stream/ { # Проксируем входящие мультиплексированные стримы proxy_pass http://video_backends; } }
Симбиоз технологий: QUIC Connection Migration против MPTCP
Главный вопрос, возникающий при проектировании таких систем: зачем одновременно использовать две технологии многопутевой доставки? Протокол HTTP/3 из коробки обладает встроенной функцией миграции соединений. Если пользователь смартфона выходит из дома, QUIC‑сессия бесшовно переключается с Wi‑Fi на сотовую связь на уровне приложения, не требуя переустановки TLS‑ключей, так как Connection ID остается неизменным.
Однако этот механизм в QUIC работает на уровне приложения и активируется в момент фиксации ухудшения связи на текущем интерфейсе. Механизм MPTCP, работая на уровне ядра Linux, управляет трафиком иначе: он связывает независимые TCP‑субпотоки через разные физические интерфейсы в единый логический пул. Это позволяет не просто переключать каналы при их падении, а одновременно агрегировать их пропускную способность и мгновенно перенаправлять пакеты в живой субпоток без задержек на уровне приложения.
Объединение HTTP/3 и MPTCP в рамках одного сервера позволяет создать ультимативный контур доставки контента. Для внешних мобильных клиентов, чьи приложения поддерживают QUIC, веб‑сервер отдает приоритет HTTP/3. В то же время для межсерверного взаимодействия, магистральных каналов дата‑центров и корпоративных клиентов, жестко зафиксированных внутри TCP‑стека, но имеющих поддержку многопоточности на уровне ОС, nginx утилизирует всю мощь параллелизма MPTCP.

