ERR_QUIC_PROTOCOL_ERROR и ERR_HTTP2_PROTOCOL_ERROR: что такое HTTP/2, HTTP/3 и QUIC и почему они ломаются
13 мин чтения
ERR_QUIC_PROTOCOL_ERROR и ERR_HTTP2_PROTOCOL_ERROR: что такое HTTP/2, HTTP/3 и QUIC и почему они ломаются

ERR_QUIC_PROTOCOL_ERROR и ERR_HTTP2_PROTOCOL_ERROR: что такое HTTP/2, HTTP/3 и QUIC и почему они ломаются

Сайт открывается в Firefox и не открывается в Chrome, curl отдаёт 200, а браузер пишет «Сайт не позволяет установить соединение». За двумя кодами стоят два протокола: HTTP/2 поверх TCP и HTTP/3 поверх QUIC и UDP. Разбираем, как браузер выбирает протокол, что ломается на сервере, в сети и у пользователя, как локализовать проблему одной командой curl и как безопасно включать и выключать HTTP/3.

ERR_HTTP2_PROTOCOL_ERROR (код -337 в Chromium) и ERR_QUIC_PROTOCOL_ERROR (код -356) — ошибки, при которых TLS-соединение с сайтом установлено, но обмен по согласованному протоколу нарушил спецификацию: HTTP/2 поверх TCP в первом случае, HTTP/3 поверх QUIC и UDP во втором. Это не «сайт недоступен» и не ошибка сертификата: сервер найден, шифрование поднято, а дальше одна из сторон прислала кадр, который другая не смогла принять. Источник — приложение с некорректными заголовками, балансировщик, антивирус или файрвол, пропускающий TCP 443 и режущий UDP 443.

Чем HTTP/1.1, HTTP/2 и HTTP/3 отличаются друг от друга

HTTP/2 сохранил семантику HTTP/1.1 (методы, коды, заголовки), но заменил текст бинарными кадрами и гоняет десятки запросов в одном TCP-соединении. HTTP/3 перенёс те же кадры с TCP на QUIC — транспорт поверх UDP со встроенным TLS 1.3, где потеря пакета тормозит один поток, а не всё соединение.

Признак HTTP/1.1 HTTP/2 HTTP/3
RFC 2068 (1997), 2616 (1999), сейчас 9112 (2022) 7540 (май 2015), сейчас 9113 (июнь 2022) 9114 (июнь 2022); QUIC — 9000 (май 2021)
Транспорт TCP, текст TCP, бинарные кадры QUIC поверх UDP 443
Мультиплексирование Нет: один запрос за раз, до 6 соединений на хост Потоки в одном соединении (nginx: 128 по умолчанию) Независимые потоки QUIC
Head-of-line blocking На уровне HTTP: очередь на соединение На уровне TCP: потерянный сегмент останавливает все потоки Нет: потеря тормозит только свой поток
Шифрование Необязательно Браузеры — только h2 поверх TLS 1.2+ Всегда: TLS 1.3 встроен в QUIC (RFC 9001)
nginx Всегда С 1.9.5 (2015); http2 on с 1.25.1 С 1.25.0 (май 2023), сборка с --with-http_v3_module
Apache httpd Всегда mod_http2 с 2.4.17; с 2.4.27 не согласуется при MPM prefork Штатного модуля нет
Cloudflare Да Да, плюс «HTTP/2 to Origin» Переключатель на всех планах; до origin HTTP/3 не ходит
Браузеры по умолчанию Все Chrome 41, Firefox 36 (2015) Chrome и Edge 87, Firefox 88, Safari 16 (по Can I use)

По данным W3Techs на 23 сентября 2026 года, HTTP/3 используют 40,8 % сайтов, HTTP/2 — 34,8 %: ошибки новых протоколов стали такими же обыденными, как ERR_CONNECTION_RESET. Разница между TCP и UDP, на которой держится различие HTTP/2 и HTTP/3, — в статье «TCP и UDP: в чём разница».

Как браузер выбирает между HTTP/1.1, HTTP/2 и HTTP/3

HTTP/2 согласуется внутри TLS-рукопожатия через расширение ALPN: клиент предлагает «h2, http/1.1», сервер выбирает один. Про HTTP/3 браузер при первом визите не знает ничего — он приходит по TCP, получает в ответе заголовок Alt-Svc с токеном h3 (RFC 7838), запоминает его и только следующие соединения пробует открыть по QUIC.

Шаг Что происходит Если не получилось
1. Первый визит: TCP + TLS, ALPN Сервер ответил «h2» — кадры HTTP/2; «http/1.1» или молчание — текстовый HTTP/1.1 Выбрал h2, а прислал текст HTTP/1.1 — ERR_HTTP2_PROTOCOL_ERROR; h2 на шифре из чёрного списка RFC 9113 — ERR_HTTP2_INADEQUATE_TRANSPORT_SECURITY (-360)
2. В ответе Alt-Svc: h3=":443"; ma=86400 Браузер запоминает: хост принимает HTTP/3 на UDP 443, срок — сутки Нет заголовка — сайт живёт на HTTP/2, даже если QUIC на сервере включён
3. Следующее соединение: гонка Chromium открывает QUIC первым, TCP откладывает на время RTT, но не больше 3 с (http_stream_factory_job_controller.cc) QUIC-рукопожатие не прошло — побеждает TCP, пользователь видит задержку
4. Пометка «broken» По broken_alternative_services.cc: после сбоя 300 с без попыток QUIC, каждый следующий сбой удваивает паузу, потолок около двух суток После починки UDP 443 часть пользователей ещё часами сидит на HTTP/2
5. Обрыв QUIC посреди запроса Заголовки ответа ещё не пришли — Chromium молча повторяет запрос по TCP (http_network_transaction.cc) Заголовки уже получены — повторить нельзя, пользователь видит ERR_QUIC_PROTOCOL_ERROR

Два вывода. ERR_QUIC_PROTOCOL_ERROR почти не встречается на первом заходе — сначала браузеру нужен Alt-Svc. И если QUIC недоступен полностью, Chrome сам откатится на TCP; ошибка появляется там, где UDP проходит частично: рукопожатие удаётся, а потом пакеты теряются или соединение сбрасывает промежуточный узел. Firefox и Safari узнают про h3 ещё из DNS-записи HTTPS (RFC 9460); в Chrome эта возможность, по обсуждениям в chromium-dev, оставалась за флагом.

Как за минуту понять, какой протокол ломается

Три запроса curl с принудительной версией отвечают на главный вопрос: сбоит HTTP/2, HTTP/3 или сайт целиком. Для HTTP/3 нужна сборка curl со словом HTTP3 в выводе curl --version.

$ curl -sI --http1.1 https://example.com/ | head -1
HTTP/1.1 200 OK

$ curl -sI --http2 https://example.com/ | head -1
HTTP/2 200

# --http3 при неудаче тихо откатывается на h2/h1;
# для диагностики нужен строгий режим
$ curl -sI --http3-only https://example.com/ | head -1
HTTP/3 200

# пример вывода, когда UDP 443 не отвечает
$ curl -sI --http3-only https://example.com/
curl: (96) QUIC connection has been shut down

# пример вывода: HTTP/2 ломается на конкретном URL
$ curl -sv --http2 -o /dev/null https://example.com/export.php
< HTTP/2 200
< content-length: 48211
< content-encoding: gzip
* HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1)
curl: (92) HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR

Коды выхода curl по libcurl-errors: 16 — сбой на уровне кадров HTTP/2, 92 — ошибка в одном потоке HTTP/2, 95 — ошибка уровня HTTP/3, 96 — QUIC-соединение не установлено. Падает только --http3-only — проблема в UDP-пути; падает --http2 при рабочем --http1.1 — в кадрах HTTP/2 на сервере или прокси. В последнем примере статус 200 пришёл, а поток закрыт с ошибкой — в DevTools это выглядит как «net::ERR_HTTP2_PROTOCOL_ERROR 200 (OK)». Второй инструмент — Alt-Svc: обещает ли сервер HTTP/3 и на каком порту.

$ curl -sI https://example.com/ | grep -i '^alt-svc'
# пример вывода: nginx с http3 on или Cloudflare
alt-svc: h3=":443"; ma=86400

# пример вывода: черновик h3-29 — современный Chrome его игнорирует
alt-svc: h3-29=":443"; ma=2592000

Третий — сам браузер. Отключите QUIC и повторите: ошибка исчезла — сломан UDP-путь. Это диагноз, а не лечение: флаг живёт на одном компьютере, у посетителей ничего не изменится.

chrome://flags/#enable-quic   # Experimental QUIC protocol → Disabled,
edge://flags/#enable-quic     # затем перезапуск браузера
about:config                  # Firefox: network.http.http3.enable = false
chrome://net-export           # лог сетевого стека с уточнением ошибки,
                              # например QUIC_NETWORK_IDLE_TIMEOUT

Откуда берётся ERR_HTTP2_PROTOCOL_ERROR

Чаще всего виноват ответ сервера, нарушающий раздел 8.2.1 RFC 9113: имена заголовков только в нижнем регистре, значения без пробелов по краям и без CR, LF, NUL, а Connection, Keep-Alive, Transfer-Encoding и Upgrade запрещены. Вторая причина — тело, длина которого не совпадает с Content-Length: по разделу 8.1.1 такой ответ malformed, и Chrome закрывает поток с PROTOCOL_ERROR, хотя статус 200 уже показал. Реже виноват объём: десятки Set-Cookie превышают лимит SETTINGS_MAX_HEADER_LIST_SIZE клиента, и Chrome отвечает ERR_RESPONSE_HEADERS_TOO_BIG (-325).

Где Причина Как проверить Как исправить
Приложение Заголовок с переносом строки, пробелом в конце или управляющим символом (Set-Cookie, Content-Disposition из пользовательских данных) curl -sD - --http1.1 показывает сырые заголовки; ошибка на конкретных URL Валидировать значения; имена файлов кодировать по RFC 8187 (filename*=UTF-8'')
Сжатие Приложение выставило Content-Length несжатого тела, а mod_deflate или gzip в прокси его сжал; вывод после header() в PHP curl -v --http2: «not closed cleanly: PROTOCOL_ERROR»; по --http1.1 size_download меньше Content-Length Сжимать на одном уровне; не задавать Content-Length вручную
Apache mod_http2 с MPM prefork и mod_php: до 2.4.27 зависшие потоки и обрывы, с 2.4.27 HTTP/2 в prefork не согласуется apachectl -V | grep MPM MPM event и PHP через php-fpm (mod_proxy_fcgi)
Балансировщик L4-балансировщик пропускает ALPN h2 до бэкенда, знающего только HTTP/1.1; прокси пробрасывает Transfer-Encoding: chunked в HTTP/2 curl --http2 --resolve напрямую в бэкенд работает, через балансировщик — нет Терминировать TLS там, где есть HTTP/2, или убрать h2 из ALPN
Клиент Антивирус или корпоративный прокси с инспекцией HTTPS переупаковывает кадры с ошибками Ошибка на многих сайтах у одного человека; сертификат выдан антивирусом Отключить сканирование HTTPS, сайт в исключения

Откуда берётся ERR_QUIC_PROTOCOL_ERROR

QUIC ломается не форматом заголовков, а транспортом: UDP 443 закрыт файрволом, провайдер или DPI режет или троттлит UDP, MTU на пути меньше требуемых 1200 байт. Общее у всех сбоев одно: по TCP тот же сайт открывается идеально, и администратор со своей машины проблемы не видит.

Причина Признак Как проверить Как исправить
UDP 443 закрыт на сервере или в security group Alt-Svc есть, --http3-only падает с 96; жалобы на медленный первый заход (до 3 с гонки) ss -ulnp на сервере; tcpdump 'udp port 443' во время запроса снаружи Открыть UDP 443 в обе стороны; пока не открыли — убрать Alt-Svc
UDP проходит частично: DPI, провайдер, корпоративный файрвол Рукопожатие удаётся, затем QUIC_NETWORK_IDLE_TIMEOUT или QUIC_TOO_MANY_RTOS; страдают все сайты с h3 у клиентов одного оператора С клиента --http3-only на несколько чужих сайтов; сменить канал на мобильный На сервере — держать исправным TCP-путь; на клиенте — отключить QUIC
MTU меньше 1228 (IPv4) или 1248 (IPv6) RFC 9000, раздел 14: путь обязан пропускать датаграммы 1200 байт; туннель с MTU 1200 QUIC не пропустит ping -M do -s 1172 host (Linux), ping -f -l 1172 (Windows): 1172 + 28 = 1200 MTU туннеля не ниже 1280
Неверный Alt-Svc Порт, куда UDP не проброшен (h3=":8443" за NAT), h3 на балансировщике без QUIC или только черновики h3-29 без RFC-версии; кэшируется на ma секунд curl -sI | grep alt-svc и --http3-only на указанный порт Отдавать токен h3 только там, где UDP доступен снаружи; на время экспериментов ma=300
Клиент: антивирус, файрвол, VPN UDP 443 заблокирован приложению; ошибка на всех сайтах с HTTP/3 у одного человека Отключить QUIC во флагах — ошибка исчезла; с другого устройства всё работает Правило для UDP 443, исключение в антивирусе, обновить VPN

Совет «проверьте UDP-порт через nc -zvu host 443» не работает: у UDP нет рукопожатия, и nc считает порт открытым, если не прилетело ICMP port unreachable. QUIC-сервер на мусор не отвечает, файрвол с DROP — тоже, так что «succeeded» будет и для рабочего сервера, и для закрытого порта. Проверяйте настоящим QUIC-клиентом или дампом на сервере:

# Слушает ли nginx UDP 443
$ ss -ulnp | grep ':443 '
# пример вывода
UNCONN 0 0 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=812,fd=11))

# Доходят ли QUIC Initial снаружи: запустите curl --http3-only
# с другой машины и смотрите на сервере
$ sudo tcpdump -ni eth0 'udp port 443' -c 4
# пример вывода: запрос пришёл, ответ ушёл — путь исправен
12:40:01.104 IP 198.51.100.7.54123 > 203.0.113.10.443: UDP, length 1200
12:40:01.106 IP 203.0.113.10.443 > 198.51.100.7.54123: UDP, length 1200

# Ничего не пришло — файрвол или security group
$ sudo ufw allow 443/udp

Как правильно включить HTTP/2 и HTTP/3 в nginx и Cloudflare

С nginx 1.25.1 HTTP/2 включается директивой http2 on, параметр listen ... http2 устарел. HTTP/3 требует сборки с --with-http_v3_module, второго listen с параметром quic на том же порту и заголовка Alt-Svc, который nginx сам не добавляет.

server {
    listen 443 ssl;
    listen 443 quic reuseport;   # reuseport — в одном server на порт

    server_name example.com;
    http2 on;
    http3 on;

    ssl_certificate     /etc/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;   # QUIC работает только с 1.3

    # 0-RTT для QUIC — с nginx 1.29.1 и OpenSSL 3.5.1+
    # ssl_early_data on;

    # add_header в location ПЕРЕКРЫВАЕТ add_header уровня server —
    # проверьте, что Alt-Svc отдаётся на всех типах URL
    add_header Alt-Svc 'h3=":443"; ma=86400' always;

    location / { proxy_pass http://127.0.0.1:8080; }
}

После nginx -t и reload прогоните три команды curl: --http2 должен вернуть «HTTP/2 200», --http3-only — «HTTP/3 200», grep по alt-svc — заголовок на каждом типе URL, включая статику из отдельных location. Отдельный сертификат для QUIC не нужен: TLS 1.3 внутри него использует те же файлы; само рукопожатие разобрано в статье «Как работает HTTPS».

В Cloudflare HTTP/3 (with QUIC) включается переключателем в Speed → Settings → Protocol Optimization на всех планах, но до origin Cloudflare ходит только по HTTP/1.1 или HTTP/2. Поэтому ERR_QUIC_PROTOCOL_ERROR у посетителя сайта за Cloudflare рождается между ним и краем сети Cloudflare, а ERR_HTTP2_PROTOCOL_ERROR может прийти и с origin — некорректные заголовки бэкенда Cloudflare транслирует дальше.

Выключайте HTTP/3 в правильном порядке

Сначала уберите Alt-Svc и дождитесь истечения ma (при 86400 — сутки), потом снимайте listen quic. Иначе браузеры с закэшированным Alt-Svc ещё сутки будут пробовать QUIC: Chrome откатится на TCP и пометит хост broken на 5 минут, но пользователи заметят задержку, а те, у кого UDP проходит частично, — ошибку.

Симптом → протокол → причина → что делать

Симптом Протокол Вероятная причина Что делать
Ошибка на одном URL (выгрузка, скачивание), остальное работает HTTP/2 Content-Length не совпадает с телом или невалидный заголовок curl -v --http2 на этот URL; править приложение или сжатие
Ошибка у одного пользователя на многих сайтах Любой Антивирус с инспекцией HTTPS, VPN, файрвол на UDP 443 Отключить защиту HTTPS, выключить QUIC во флагах
Первое открытие медленное (2–3 с), потом быстро QUIC Alt-Svc обещает h3, UDP 443 закрыт, Chrome ждёт до 3 с и уходит на TCP Открыть UDP 443 или убрать Alt-Svc
Ошибка у клиентов одного оператора или офиса QUIC DPI, корпоративный файрвол, MTU туннеля Сравнить из нескольких сетей; ping -M do -s 1172; сервер не трогать

Как узнать о протокольной ошибке раньше пользователей

Обе ошибки избирательны: ломается конкретный URL, протокол или сеть, а «сайт в целом» отвечает 200. Нужны регулярные запросы к проблемным URL из нескольких сетей с историей по каждой точке.

Пруфен выполняет HTTP-проверки с интервалом от 2 минут из 20 точек мониторинга — 17 в России, а также Минск, Астана и Испания. Мониторинг фиксирует, что страница перестала отдаваться с ожидаемым кодом, а сравнение по 20 точкам показывает, у всех ли: падает из всех локаций — ищите на сервере заголовки и Content-Length; из части — смотрите на сеть между этими регионами и сервером. В настройках HTTP-проверки задаются метод, заголовки и ожидаемый код ответа, чтобы проверять именно проблемный URL.

Сломанная выгрузка заметна за один интервал проверки

Пруфен присылает уведомление в Email, Telegram или Макс, как только проверка перестала проходить, и второе — когда страница снова отдаётся. Если ресурс так и не поднялся, подключается голосовой звонок. Время обнаружения равно интервалу проверки. Бесплатный план включает 5 проверок — хватит на главную, API и страницу экспорта, которая ломается после каждого обновления.

Частые вопросы

Нужно ли вообще включать HTTP/3 на своём сайте?

За Cloudflare или другим CDN — да, одним переключателем. На собственном nginx включайте, когда готовы держать открытым UDP 443 и следить за ним так же, как за TCP: выигрыш заметен на мобильных сетях, где потери пакетов останавливают HTTP/2 целиком. Если UDP открыть нельзя, не отдавайте Alt-Svc — обещанный, но недоступный h3 хуже честного HTTP/2.

Почему ошибка только в Chrome, а в Firefox сайт открывается?

У браузеров разный кэш Alt-Svc и разная логика отката: Firefox мог уже пометить HTTP/3 для хоста сломанным и ходить по TCP, а Chrome ещё пробует QUIC и ловит ошибку посреди ответа. Для HTTP/2 разница в строгости: Chromium отклоняет ответ с несовпадающим Content-Length, другой движок может дочитать его до закрытия соединения. Сломан сервер или путь, а не браузер.

QUIC — это UDP?

QUIC работает поверх UDP, но сам делает всё, чего в UDP нет: подтверждения, повторную отправку, контроль перегрузки, потоки и шифрование. UDP нужен, чтобы пакеты проходили через существующие маршрутизаторы и NAT — новый протокол уровня IP интернет бы не пропустил. Для файрвола QUIC — поток UDP на порт 443, и именно правила для UDP решают, будет ли HTTP/3 работать.

Как отключить QUIC на сервере, не ломая сайт?

В nginx уберите add_header Alt-Svc во всех location, сделайте reload и подождите срок ma — браузеры перестанут пытаться открыть QUIC. Затем уберите listen ... quic и http3 on. Если снять listen quic сразу, браузеры откатятся на TCP, но соединения до истечения кэша будут медленнее. В Cloudflare достаточно выключить переключатель HTTP/3.

Была ли статья полезна?

Начните мониторинг прямо сейчас

Бесплатный тариф — до 5 проверок. 150 ₽ на баланс при регистрации.

Создать аккаунт