1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 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 и запрет хоп-хоп-заголовков - листай до «Теперь сам».

Сначала ответь сам

/etc/nginx/conf.d/sites.confhttp
server {
    listen 9443 ssl http2;         # старый синтаксис, только у этого блока
    server_name shop.local;
}
server {
    listen 9443 ssl;
    server_name blog.local;
    http2 off;                     # у соседа явно выключили
}

По какому протоколу ответит blog.local?

↳  выводcurl --http2 https://blog.local:9443/
blog proto=HTTP/2.0

По HTTP/2. Параметр listen относится к сокету, а сокет один на порт для всех блоков - и новая директива его не перебивает. Именно поэтому параметр и объявили устаревшим:

↳  выводnginx -t
[warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead

На что это похоже

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

Разделили - и каждый управляет своей комнатой. Ровно это и сделали с HTTP/2: перенесли настройку с сокета на блок.

Механизм: где живёт настройка

listen ... http2 - настройка СОКЕТА сокет 0.0.0.0:443 shop blog включилось обоим http2 on - настройка БЛОКА shop: on blog: off каждый сам за себя
Один сокет на порт - поэтому старый параметр не мог работать иначе.

Правило

/etc/nginx/nginx.confhttp
http2 on;                       # всем блокам сразу
/etc/nginx/conf.d/legacy.confhttp
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
↳  выводcurl -sI --http2 https://shop.local/ | head -1
HTTP/2 200

В логе - переменная $server_protocol, она есть и в формате combined:

↳  выводtail -1 /var/log/nginx/access.log
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_timeoutkeepalive_timeout, http2_max_requestskeepalive_requests, http2_recv_timeoutclient_header_timeout, http2_max_field_size и http2_max_header_sizelarge_client_header_buffers. Из живых стоит знать http2_max_concurrent_streams (по умолчанию 128 - столько запросов клиент может держать в полёте).

Зачем это в работе

Отдельный сюжет - HTTP/2 к бэкенду. Браузеры договариваются о нём через ALPN (поле внутри TLS-рукопожатия, где стороны называют, на каком протоколе говорить дальше) и открытым текстом не умеют, а внутри инфраструктуры это применяют - и с 1.29.4 nginx так умеет:

/etc/nginx/conf.d/shop.local.confhttp server location /api/
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.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.