Выберите продукт

Nginx HTTP/2: как разбирать ERR_HTTP2_PROTOCOL_ERROR и «http2 framing layer» на API

Если браузер рвёт соединение с ERR_HTTP2_PROTOCOL_ERROR, а клиенты пишут про «http2 framing layer», обычно виноваты ALPN/TLS и маршрутизация по SNI, настройки прокси-буферов, keepalive/таймауты или неправильное проксирование gRPC. Ниже — чек‑лист с командами и безопасными правками Nginx.
Nginx HTTP/2: как разбирать ERR_HTTP2_PROTOCOL_ERROR и «http2 framing layer» на API

ERR_HTTP2_PROTOCOL_ERROR в браузере и сообщения вида http2 framing layer в клиентах (curl, Go, Node, gRPC) — одна из самых неприятных категорий инцидентов: ошибка «плавающая», логов мало, а внешне всё похоже на случайные обрывы.

Почти всегда причина в одном из трёх слоёв:

  • TLS/ALPN: клиент ожидает h2, а сервер/прокси согласовал другой протокол или отдал не тот трафик по «h2»-каналу.

  • Проксирование: промежуточный узел режет/буферизует ответ или неверно проксирует gRPC, и клиент видит протокольный сбой.

  • Лимиты и время жизни соединений: keep-alive, таймауты и лимиты запросов закрывают соединение в «неудачный» момент, что в HTTP/2 часто выглядит как протокольная ошибка.

Ниже — пошаговый чек-лист, который обычно приводит к первопричине без гадания.

Как проявляется проблема: отличаем HTTP/2 от «просто 502»

Типовые симптомы:

  • Chrome/Edge: ERR_HTTP2_PROTOCOL_ERROR на конкретном эндпоинте API или при нагрузке.

  • curl: HTTP/2 stream 0 was not closed cleanly, PROTOCOL_ERROR, иногда received GOAWAY «внезапно».

  • Go/Node: http2: server sent GOAWAY and closed the connection или «http2 framing layer».

  • gRPC: transport: error while dialing, RST_STREAM, INTERNAL: received RST_STREAM.

Ключевой момент: клиент жалуется именно на протокол, а не на HTTP-код. Это почти всегда означает несоответствие ожиданий по HTTP/2 на одном из участков цепочки.

Быстрая проверка: действительно ли до сервера доезжает HTTP/2

Сначала нужно понять, где именно «ломается» HTTP/2: на внешнем входе, на балансере, на Nginx, на апстриме.

1) Проверяем ALPN и выбранный протокол

HTTP/2 поверх TLS включается через ALPN (Application-Layer Protocol Negotiation). Если ALPN не согласован, клиент уйдёт в HTTP/1.1 или оборвёт соединение, если ожидал h2 (особенно для gRPC).

curl -vk --http2 https://api.example.com/health

В выводе ищите строки про ALPN и протокол. Если согласован h2 — хорошо. Если согласован только http/1.1, а вы уверены, что «включали HTTP/2», разбираемся с конфигом и фронтовыми прокси.

2) Если есть промежуточный L4 прокси: вспоминаем про ssl_preread

Частая схема: перед «основным» Nginx стоит stream-балансер (L4), который маршрутизирует по SNI, а дальше уже TLS терминируется на бекенде. В такой архитектуре используют ssl_preread в stream-контексте.

ssl_preread сам по себе не «включает HTTP/2». Он читает ClientHello (SNI/ALPN) для маршрутизации. Ошибки начинаются, когда L4 отправляет трафик на неправильный бекенд или на бекенд без нужного ALPN/сертификата.

Проверьте, что поток с SNI api.example.com стабильно попадает туда, где в server для 443 действительно настроен http2 и корректная цепочка сертификата.

3) Минимальная телеметрия: что думает сам Nginx

Убедитесь, что в access-логах вы видите протокол. В Nginx можно логировать $server_protocol (для HTTP/2 будет HTTP/2.0), а также времена ответа апстрима.

Пример формата (как ориентир):

log_format main_ext '$remote_addr host=$host req="$request" '
                   'status=$status bytes=$body_bytes_sent '
                   'proto=$server_protocol rt=$request_time '
                   'urt=$upstream_response_time ua="$http_user_agent"';

Если в момент ошибки в логах видите proto=HTTP/1.1, а клиенты жалуются на HTTP/2 — это красный флаг: где-то до Nginx происходит несогласование протокола или неправильная маршрутизация.

Пример access-лога Nginx с протоколом HTTP/2 и таймингами апстрима

Самые частые причины ERR_HTTP2_PROTOCOL_ERROR в связке Nginx + API

Причина 1: HTTP/2 включён не там (или включён «не тот h2»)

Для HTTPS-виртуалхоста Nginx HTTP/2 включается на listen 443 ssl http2;. Если у вас несколько server-блоков, легко «промахнуться»: один блок обслуживает редирект или дефолтный vhost, другой — API. В итоге часть запросов попадает в vhost без HTTP/2 или с неправильным сертификатом.

Что проверить:

  • Порядок server-блоков и server_name (нет ли «дефолтного» блока, который перехватывает SNI).

  • Нет ли конфликта на уровне stream-прокси (SNI уходит не туда).

  • Сертификат и цепочка (при проблемах рукопожатия и ретраях клиенты иногда «маскируют» первичную ошибку под протокольную).

Причина 2: gRPC проксируется как обычный HTTP

Если ваш API частично или полностью gRPC, проксирование через proxy_pass часто приводит к ошибкам уровня http2 framing layer. gRPC требует корректного HTTP/2 и правильного проксирования через grpc_pass (а также корректных таймаутов).

Признаки:

  • REST-эндпоинты работают, а gRPC-метод падает.

  • Падает только на больших сообщениях или стриминге.

  • В логах апстрим «как будто ответил», но клиент всё равно видит протокольную ошибку.

Минимальная рабочая схема для gRPC в Nginx (как ориентир):

server {
  listen 443 ssl http2;
  server_name api.example.com;

  location /mypackage.MyService/ {
    grpc_pass grpc://127.0.0.1:50051;
    grpc_read_timeout 300s;
    grpc_send_timeout 300s;
  }
}

Если апстрим говорит h2c (HTTP/2 cleartext) — используйте grpc://. Если TLS до апстрима — grpcs:// и отдельные настройки доверия/проверки сертификата.

Если у вас смешанный стек (gRPC-Web, Envoy и т.п.), полезно свериться с архитектурой проксирования: как проксировать gRPC-Web через Envoy.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Причина 3: proxy_buffering и «неожиданная» буферизация для API/стриминга

Директива proxy_buffering чаще всплывает при SSE/WebSocket и больших ответах. Но на HTTP/2 она тоже влияет: Nginx может буферизовать ответ от апстрима и отдавать клиенту «рывками», а при таймаутах/обрывах это превращается в протокольные симптомы.

Где чаще всего больно:

  • SSE (Server-Sent Events) и длинные ответы.

  • Большие JSON, которые генерируются долго.

  • Апстрим отдаёт поток (chunked/stream), а фронт пытается его «собрать».

Практический подход: для стриминговых эндпоинтов пробуйте отключать буферизацию и поднимать таймаут чтения:

location /events/ {
  proxy_pass http://backend;
  proxy_buffering off;
  proxy_cache off;
  proxy_read_timeout 3600s;
}

Для «обычного» API отключать proxy_buffering повсеместно не нужно: это может ухудшить производительность. Но точечно — очень часто лечит нестабильные ERR_HTTP2_PROTOCOL_ERROR на длинных ответах.

Причина 4: keepalive_requests и разрывы соединения «между запросами»

HTTP/2 держит одно TCP/TLS-соединение и мультиплексирует запросы. Если вы агрессивно ограничиваете жизнь соединения, клиент может получить закрытие в момент, когда планировал открыть новый stream. В браузере это иногда выглядит как ERR_HTTP2_PROTOCOL_ERROR, особенно при параллельных запросах.

Проверьте:

  • keepalive_requests (сколько запросов разрешено на одно соединение).

  • keepalive_timeout (сколько живёт keep-alive).

  • не прилетает ли клиенту GOAWAY слишком рано.

Если соединения часто закрываются по лимиту, увеличьте keepalive_requests для API, где много мелких запросов. Это снижает churn соединений и уменьшает вероятность гонок закрытия.

Причина 5: апстрим «сыпется» под паттерн HTTP/2 (особенно через прокси)

Даже если наружу Nginx отдаёт HTTP/2, внутренняя часть часто остаётся HTTP/1.1 до апстрима. Тогда реальная проблема может быть в апстриме: он не выдерживает burst запросов, уходит в таймауты, закрывает соединения или отдаёт некорректные заголовки.

Что сделать прагматично:

  • Сравнить поведение при curl --http1.1 и curl --http2 на одном и том же эндпоинте.

  • Проверить корреляцию с RPS, размером ответов, временем генерации.

  • Посмотреть error-лог Nginx на «upstream prematurely closed connection» и таймауты.

Пошаговый сценарий диагностики (рабочий чек-лист)

Шаг 1. Воспроизводим с curl и фиксируем протокол

curl -svk --http2 https://api.example.com/v1/ping
curl -svk --http1.1 https://api.example.com/v1/ping

Если HTTP/1.1 стабилен, а HTTP/2 нет — проблема почти точно в h2-цепочке (ALPN, прокси, gRPC, лимиты, закрытие соединения).

Шаг 2. Ищем GOAWAY/RST в поведении клиента

В мире HTTP/2 «красные кнопки» — это GOAWAY (сервер уходит) и RST_STREAM (сбрасывается поток). Браузер часто не показывает детали, но в curl/клиентских логах их видно.

Если это gRPC — смотрите коды статуса и сообщения transport-слоя: они часто точнее указывают, на каком участке обрыв.

Шаг 3. Временно упрощаем цепочку: прямой доступ к Nginx

Если перед Nginx стоит CDN/балансер/ingress — попробуйте на время (в тестовой зоне или по отдельному хостнейму) дать прямой доступ к Nginx. Цель — понять, генерирует ли проблему сам Nginx/апстрим, или это артефакт внешнего прокси.

Шаг 4. Проверяем конфигурацию server-блоков на 443

Минимальный набор вопросов:

  • Есть ли ровно тот server, который матчится по SNI на проблемный домен?

  • На нужном ли listen включён http2?

  • Нет ли редиректов/перехвата в другом vhost?

Полезно прогнать синтаксическую проверку и распечатать полный конфиг:

nginx -t
nginx -T | sed -n '1,200p'

Шаг 5. Если есть stream + ssl_preread: валидируем маршрутизацию

При L4-маршрутизации «протокольная» ошибка может означать просто «попали не туда». Например, SNI уехал на бекенд, где ожидают другой протокол (или plain HTTP), и клиент получает мусор вместо HTTP/2-фреймов.

На время диагностики удобно выделить отдельный порт/бекенд и убедиться, что SNI точно идёт куда нужно. Если параллельно вы экспериментируете с терминацией HTTP/3/QUIC на edge, сравните подходы: терминация HTTP/3/QUIC на балансере.

Схема маршрутизации по SNI/ALPN через L4-прокси к Nginx

Набор «безопасных» правок, которые часто стабилизируют HTTP/2 для API

Это не универсальная «таблетка», но набор действий, который часто приводит конфиг в предсказуемое состояние:

  1. Явно включить HTTP/2 на нужном 443-vhost: listen 443 ssl http2;

  2. Для gRPC использовать grpc_pass, а не proxy_pass.

  3. Точечно отключить proxy_buffering для стриминговых эндпоинтов и увеличить таймауты чтения.

  4. Проверить и при необходимости увеличить keepalive_requests и keepalive_timeout.

  5. Убедиться, что TLS согласует ALPN (h2) и нет конфликтов SNI/сертификатов.

Если проблема всплыла после перевыпуска сертификата или изменения цепочки, проверьте корректность установки и совместимость. Для продакшн-сервисов обычно удобнее держать управляемую историю сертификатов и обновления заранее — в том числе через SSL-сертификаты с понятным циклом продления.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Почему ошибка называется «http2 framing layer» и при чём тут Nginx

HTTP/2 — бинарный протокол, данные идут «фреймами». Сообщение «http2 framing layer» обычно означает, что клиент получил последовательность байт, которая не соответствует формату фреймов HTTP/2, либо получил корректные фреймы, но в неожиданном порядке.

В контексте Nginx чаще всего это происходит, когда:

  • на входе договорились о h2, но далее в цепочке внезапно отдали HTTP/1.1 (неверный vhost, дефолтный server, ошибка SNI-маршрутизации);

  • gRPC пошёл через неправильный location или через proxy_pass;

  • соединение оборвали в момент активных потоков (лимиты/keepalive/таймауты), а клиент интерпретировал это как протокольный сбой.

Мини-памятка: что собирать для разбора инцидента

Чтобы быстро локализовать проблему (и не возвращаться к ней через месяц), соберите в один пакет:

  • воспроизведение curl -vk --http2 (полный вывод);

  • время инцидента и корреляцию с RPS/нагрузкой;

  • access/error логи Nginx за интервал (с $server_protocol и upstream timings);

  • конкретный server-блок для домена и location проблемного эндпоинта;

  • если используется stream + ssl_preread — конфиг stream и список бекендов, куда может уйти трафик.

Как только у вас появляется связка «curl-вывод + $server_protocol в логах + точный vhost», ERR_HTTP2_PROTOCOL_ERROR перестаёт быть мистикой и превращается в обычную задачу конфигурации.

Вывод

ERR_HTTP2_PROTOCOL_ERROR и «http2 framing layer» почти всегда указывают не на «плохой интернет», а на несогласованность протокола и ожиданий между клиентом и вашей цепочкой прокси/апстримов. Начинайте с ALPN и маршрутизации по SNI, отдельно проверьте gRPC, затем разберите буферизацию (proxy_buffering) и ограничения жизни соединений (keepalive_requests и таймауты). Этот порядок обычно даёт результат быстрее всего.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: как исправить Host key verification failed в Ansible при смене IP OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Host key verification failed в Ansible при смене IP

Ошибка Host key verification failed в Ansible на Debian и Ubuntu обычно возникает после переустановки сервера, смены IP или повтор ...
Debian/Ubuntu: duplicate address detected, DAD failed IPv6 — причины и исправление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: duplicate address detected, DAD failed IPv6 — причины и исправление

Сообщения duplicate address detected и DAD failed в Debian/Ubuntu означают, что IPv6-адрес не прошёл проверку уникальности в локал ...
Debian/Ubuntu: как исправить swapoff: Device or resource busy при отключении swap OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить swapoff: Device or resource busy при отключении swap

Ошибка swapoff: Device or resource busy в Debian и Ubuntu обычно связана не с «битым» swap, а с нехваткой RAM, zram, автоподключен ...