# теория · шаг 2 из 5
Директива http2, а не параметр listen
Коротко
С 1.25.1 HTTP/2 включается директивой http2 on;, а параметр listen ... http2
устарел - он относился к сокету, а не к блоку.
- По умолчанию
http2 off: после переезда со старого синтаксиса протокол легко потерять молча. - Старый параметр включал HTTP/2 всем блокам порта - и
http2 off;у соседа этого не отменяет. Проверено. - Хоп-хоп-заголовки по h2 запрещены:
connection,keep-alive,transfer-encoding,upgradeдают 400. Разрешён толькоte: trailers. - Проверить это через curl нельзя: он выбрасывает такой заголовок сам, показывая его в своём выводе.
Если знаешь про http2 on и запрет хоп-хоп-заголовков - листай до «Теперь сам».
Сначала ответь сам
server {
listen 9443 ssl http2; # старый синтаксис, только у этого блока
server_name shop.local;
}
server {
listen 9443 ssl;
server_name blog.local;
http2 off; # у соседа явно выключили
}
По какому протоколу ответит blog.local?
blog proto=HTTP/2.0
По HTTP/2. Параметр listen относится к сокету, а сокет один на порт для
всех блоков - и новая директива его не перебивает. Именно поэтому параметр и
объявили устаревшим:
[warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead
На что это похоже
Общий рубильник на этаже и выключатели в кабинетах. Пока освещением управляет рубильник, любой кабинет может включить свет всем сразу, а выключить у себя - нет: его выключатель стоит до рубильника, а не после.
Разделили - и каждый управляет своей комнатой. Ровно это и сделали с HTTP/2: перенесли настройку с сокета на блок.
Механизм: где живёт настройка
Правило
http2 on; # всем блокам сразу
server {
listen 443 ssl;
server_name legacy.local;
http2 off; # а этому - нет
}
Директива живёт по обычным правилам наследования: http, server, значение с
уровня выше. Ставить http2 on; в http - нормальная практика.
Помни про умолчание: http2 off. Директива не наследует старое поведение и
ничего не включает сама. После переезда со старого синтаксиса это ловят не
сразу: предупреждение исчезло, протокол тоже.
Разбор: как убедиться, что он включился
Три способа, от быстрого к надёжному:
curl -sI --http2 https://shop.local/ | head -1
HTTP/2 200
В логе - переменная $server_protocol, она есть и в формате combined:
198.51.100.7 - - [08/Aug/2026:12:41:02 +0300] "GET /catalog HTTP/2.0" 200 8123
И проверка, что модуль собран - на минимальных сборках его может не быть:
nginx -V 2>&1 | tr ' ' '\n' | grep http_v2
Разбор: что по HTTP/2 передать нельзя
Хоп-хоп-заголовки (от hop - «участок пути»; описывают одно соединение, а не
весь маршрут: Connection, Keep-Alive, Transfer-Encoding, Upgrade) в
мультиплексированном протоколе бессмысленны. С версии 1.31.0 nginx на такой
запрос отвечает ошибкой. Замер настоящим h2-клиентом:
| заголовок в запросе | ответ |
|---|---|
| без него | 200 |
connection: Upgrade |
400 |
connection: close |
400 |
keep-alive: timeout=5 |
400 |
transfer-encoding: chunked |
400 |
upgrade: websocket |
400 |
te: trailers |
200 |
te: gzip |
400 |
te: trailers - единственное разрешённое значение единственного разрешённого из
этого списка заголовка (te просит разрешить «трейлеры» - заголовки после тела
ответа).
Проверить это через curl не получится
curl показывает заголовок в своём выводе строкой > Connection: Upgrade, а на
провод его не кладёт: его h2-библиотека выбрасывает запрещённое поле сама.
Ответ приходит 200, и легко решить, что nginx ничего не запрещает. Таблица выше
снята клиентом nghttp, который отправляет что попросили.
Что ломается без этого
Практическое следствие уже знакомо по седьмому разделу: WebSocket поверх
HTTP/2 обычным апгрейдом не поднимается. Браузер это знает и открывает
WebSocket отдельным соединением HTTP/1.1, поэтому в логах рядом с HTTP/2.0
всегда будут строки HTTP/1.1 от /ws/ - это норма, а не поломка.
Обидный случай - «мы включили HTTP/2, и чат отвалился». Обычно оказывается, что
чат ходил не через браузерный WebSocket, а самописным клиентом, который слал
Connection: Upgrade по h2. Раньше это молча игнорировалось, теперь запрос
отклоняется. Лечится переводом клиента на HTTP/1.1 для WebSocket.
Отдельно про устаревшие http2_*: настройки, дублировавшие общие, убраны в
пользу общих (1.19.7). http2_idle_timeout → keepalive_timeout,
http2_max_requests → keepalive_requests, http2_recv_timeout →
client_header_timeout, http2_max_field_size и http2_max_header_size →
large_client_header_buffers. Из живых стоит знать
http2_max_concurrent_streams (по умолчанию 128 - столько запросов клиент может
держать в полёте).
Зачем это в работе
Отдельный сюжет - HTTP/2 к бэкенду. Браузеры договариваются о нём через ALPN (поле внутри TLS-рукопожатия, где стороны называют, на каком протоколе говорить дальше) и открытым текстом не умеют, а внутри инфраструктуры это применяют - и с 1.29.4 nginx так умеет:
proxy_pass http://app:8000;
proxy_http_version 2;
Нужно редко: обычно достаточно keepalive поверх HTTP/1.1 (восьмой раздел). Но если бэкенд - gRPC-подобный сервис с длинными потоками, вариант рабочий.
Теперь сам
На сервере пять сайтов. В конфиге одного из них стоит listen 443 ssl http2;,
у остальных четырёх - обычный listen 443 ssl; без единого упоминания
протокола. Сколько сайтов отдаёт HTTP/2 и что будет, если убрать параметр из
первого?
Все пять: параметр включил протокол на весь сокет. Уберёшь его - HTTP/2
пропадёт сразу у всех пяти, потому что умолчание директивы http2 - off, и
её никто не написал. Правильный порядок: сначала добавить http2 on; в
контекст http, убедиться, что все пять по-прежнему отдают HTTP/2.0, и только
потом убирать устаревший параметр.
Главное
С 1.25.1 HTTP/2 включается директивой http2 on; в http или server;
параметр listen ... http2 устарел, относится к сокету и включает протокол всем
блокам порта - причём http2 off; у соседа этого не отменяет. Умолчание
директивы - off, поэтому при переезде протокол легко потерять молча.
Хоп-хоп-заголовки в h2 запрещены и дают 400, кроме te: trailers; проверять это
надо настоящим h2-клиентом, потому что curl выбрасывает такой заголовок сам.
Отсюда же WebSocket живёт отдельным соединением HTTP/1.1.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий