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_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 так умеет:

/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, и только потом убирать устаревший параметр.

Главное

Включай HTTP/2 директивой http2 в нужном контексте и проверяй согласованный протокол у каждого сайта. Устаревший параметр listen влияет на общий сокет, поэтому соседний server-блок тоже может начать принимать HTTP/2. Успешный запрос обычного клиента не доказывает выбранную версию HTTP.

Комментарии

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

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

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