HTTP/1.1, HTTP/2 и HTTP/3
Мультиплексирование, сжатие заголовков, QUIC и что всё это значит для скорости.
За 30 секунд
Смысл HTTP не менялся десятилетиями, а вот способ доставки сообщений изменился. HTTP/1.1 отправляет по одному запросу за раз на соединение, HTTP/2 — много запросов параллельно по одному TCP-соединению, а HTTP/3 делает то же самое поверх QUIC на UDP, чтобы не застревать при потере пакетов. Включение новых версий обычно занимает несколько строк конфигурации и ускоряет загрузку страниц, особенно на мобильных.
Почему я это использую
- Я включаю HTTP/2 и HTTP/3 на своих сайтах, потому что это дешёвое ускорение для каждого посетителя.
- Понимание того, как работает каждая версия, подсказывает, от каких старых трюков оптимизации пора отказаться, — например, от domain sharding и огромных бандлов.
- Больше всего от HTTP/3 выигрывают мобильные пользователи в нестабильных сетях, а многими моими продуктами пользуются с телефонов.
- Это помогает мне рассуждать о функциях реального времени и долгоживущих соединениях за nginx и CDN.
Как это работает
01HTTP/1.1: по одному запросу за раз
HTTP/1.1 (1997, уточнён в RFC 9112) — текстовый протокол. Keep-alive позволяет переиспользовать соединение, но на каждом соединении запросы обслуживаются строго один за другим: если один ответ медленный, всё, что за ним, ждёт. Это head-of-line blocking на уровне HTTP. Браузеры обходят его, открывая около шести соединений на хост, а разработчики придумывали трюки: domain sharding на несколько hostname'ов, спрайты изображений и склейку всего JavaScript в один файл. Заголовки передаются обычным текстом с каждым запросом, поэтому cookie и user agent повторяются тысячи раз. Аналогия: одна касса, где следующий покупатель ждёт, пока закончит предыдущий, поэтому магазины открывают шесть касс.
02HTTP/2: мультиплексирование и HPACK
HTTP/2 (2015, сейчас RFC 9113) сохраняет те же методы, заголовки и статус-коды, но меняет формат передачи на бинарные фреймы. Его главная особенность — мультиплексирование: множество запросов и ответов одновременно идут по одному TCP-соединению как независимые потоки, перемежаясь фрейм за фреймом. Один медленный ответ больше не блокирует остальные на уровне HTTP. HPACK сжимает заголовки с помощью таблицы ранее отправленных значений, поэтому повторяющиеся cookie и заголовки стоят всего несколько байт. Приоритизация позволяет браузеру сообщить, что важнее. Server push существовал, но его убрали из браузеров, потому что он редко помогал; его заменяет 103 Early Hints с preload. На практике браузеры используют HTTP/2 только поверх TLS, договариваясь о нём через ALPN во время рукопожатия.
- Одно соединение на origin вместо шести — меньше рукопожатий.
- Domain sharding теперь вредит: он заставляет открывать лишние соединения.
- Много маленьких файлов — это нормально; делите бандлы по маршрутам вместо одного гигантского файла.
03Оставшаяся проблема: head-of-line blocking в TCP
HTTP/2 устранил блокировку на уровне HTTP, но не ниже. TCP гарантирует, что байты приходят по порядку. Если теряется один пакет, TCP задерживает всё, что идёт после него, до повторной передачи, — даже данные, относящиеся к другим потокам. В чистой офисной сети это редкость. В мобильных сетях с потерей пакетов 1–2% один потерянный пакет тормозит все изображения, скрипты и API-вызовы на этом соединении. В плохих условиях HTTP/2 на одном соединении может быть даже медленнее, чем HTTP/1.1 на шести. Кроме того, TCP живёт в ядре операционной системы, поэтому его улучшение занимает годы обновлений ОС. Вот почему понадобился новый транспорт.
04HTTP/3: QUIC поверх UDP
HTTP/3 (RFC 9114, 2022) работает поверх QUIC (RFC 9000) — транспорта, построенного на UDP. QUIC сам реализует надёжность, управление перегрузкой и потоки, и каждый поток независим: потерянный пакет задерживает только свой поток. TLS 1.3 встроен в QUIC, поэтому новому соединению нужен один круговой путь вместо двух-трёх у TCP плюс TLS, а возобновлённые соединения могут отправлять данные сразу (0-RTT; его стоит ограничивать безопасными идемпотентными запросами, потому что такой запрос можно воспроизвести повторно). Соединения идентифицируются по connection ID, а не по IP и порту, поэтому телефон, переключившийся с Wi-Fi на мобильный интернет, сохраняет соединение. Заголовки сжимаются QPACK — вариантом HPACK. Браузеры узнают о HTTP/3 из заголовка Alt-Svc или DNS-записи HTTPS и откатываются на HTTP/2, если UDP 443 заблокирован.
05Как включить в nginx и Cloudflare
В nginx 1.25.1 и новее HTTP/2 включается директивой http2 (старый параметр listen ... http2 устарел), а HTTP/3 — quic-listener'ом на том же порту при сборке с модулем QUIC, который есть в текущих официальных пакетах. Анонсируйте HTTP/3 заголовком Alt-Svc и откройте UDP 443 в файрволе. В Cloudflare HTTP/2 и HTTP/3 для посетителей включены по умолчанию; проверить их можно в настройках Speed или Network. Соединение от Cloudflare до вашего origin — отдельное: оно использует HTTP/1.1 или HTTP/2 поверх TCP, поэтому origin за Cloudflare не нуждается в HTTP/3. Проверяйте через curl или колонку Protocol во вкладке Network браузера.
server {
listen 443 ssl;
listen 443 quic reuseport; # reuseport only once per port
http2 on;
http3 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
}
# sudo ufw allow 443/udp
# curl -sI --http2 https://example.com/ | head -1
06Что это значит для работы над производительностью
Когда HTTP/2 или HTTP/3 включены, сосредоточьтесь на том, что по-прежнему стоит времени. Круговые пути всё ещё важны, поэтому держите критический путь коротким: делайте preload главного изображения и основного шрифта, избегайте длинных цепочек редиректов и отдавайте контент с CDN рядом с пользователями. Байты тоже важны, поэтому сжимайте и отправляйте меньше JavaScript. Склейка файлов менее важна, но тысячи крошечных модулей всё равно дают накладные расходы; оптимум — разбиение бандлов по маршрутам. Избегайте domain sharding и лишних сторонних origin'ов, ведь каждому новому origin нужны DNS, соединение и TLS; для тех, без которых не обойтись, используйте preconnect. Измеряйте на реальных пользователях: выигрыш от HTTP/3 заметен в основном в медленных сетях с потерями, а не на вашем быстром десктопе.
Когда применять
- HTTP/2: на каждом HTTPS-сайте и API — сегодня нет реальных причин его не включать.
- HTTP/3: публичные сайты с большим числом мобильных пользователей или пользователей в далёких сетях с потерями.
- За CDN: пусть CDN говорит с посетителями по HTTP/3, а с origin — по HTTP/2 или HTTP/1.1.
Когда не стоит
- Не гонитесь за HTTP/3 для внутренних сервисов в одном дата-центре: выигрыш крошечный, а отлаживать UDP сложнее.
- Корпоративные сети иногда блокируют UDP 443, поэтому у HTTP/3 всегда должен быть fallback на HTTP/2.
Альтернативы
- WebSockets
Долгоживущий двусторонний канал; работает через upgrade в HTTP/1.1 или extended CONNECT в HTTP/2.
- gRPC
Использует потоки HTTP/2 для типизированного RPC между сервисами.
- WebTransport
Новый API на HTTP/3 для потоков и датаграмм с низкой задержкой; пока ещё развивается.
Лучшие практики
- Включите HTTP/2 везде, а HTTP/3 — на публичных edge-серверах, вместе с TLS 1.3.
- Открывайте UDP 443 в файрволе, когда включаете HTTP/3 на своём сервере.
- Откажитесь от domain sharding и спрайтов; вместо этого делите бандлы по маршрутам.
- Используйте preconnect для важных сторонних origin'ов и убирайте ненужные.
- Используйте 103 Early Hints или preload вместо удалённого server push.
- После каждого изменения конфигурации проверяйте согласованный протокол в DevTools или через curl.
Частые ошибки
- Включить quic-listener, но забыть открыть UDP 443, из-за чего браузеры молча остаются на HTTP/2.
- Добавить reuseport в несколько server-блоков для одного порта, из-за чего nginx не запускается.
- Сохранять трюки эпохи HTTP/1.1 вроде sharding, которые теперь замедляют HTTP/2.
- Разрешать 0-RTT для запросов, меняющих данные, открывая их для повторного воспроизведения.
Глубже
07Приоритизация: от деревьев зависимостей к Extensible Priorities
Мультиплексирование порождает новый вопрос: если двадцать ответов делят одно соединение, какие байты идут первыми? Изначально HTTP/2 позволял клиентам строить дерево зависимостей с весами, но оно было сложным, браузеры использовали его непоследовательно, а многие серверы игнорировали, поэтому RFC 9113 объявил его устаревшим. На смену пришли Extensible Priorities (RFC 9218), которые используют HTTP/2 и HTTP/3. Клиент отправляет простой заголовок или фрейм Priority: срочность u от 0 (самая высокая) до 7 и флаг incremental i, означающий, что ответ полезен по частям, как прогрессивное изображение. Серверы и CDN планируют отправку данных соответственно, так что CSS и LCP-изображение могут обогнать большой скрипт аналитики. На странице повлиять на то, что отправляет браузер, можно через fetchpriority и preload. Если где-то на пути приоритизация сломана, мультиплексирование может даже задерживать критичные файлы за большими.
GET /css/main.css HTTP/2
priority: u=0
GET /img/hero.avif HTTP/2
priority: u=1, i
GET /js/analytics.js HTTP/2
priority: u=5
08Flow control и атаки, специфичные для HTTP/2
У HTTP/2 и QUIC есть управление потоком на уровне stream и на уровне соединения: каждая сторона объявляет окно байтов, которое готова принять, и отправитель должен дождаться WINDOW_UPDATE, прежде чем слать больше. Маленькие окна по умолчанию могут ограничивать пропускную способность на быстрых длинных каналах, поэтому серверы их настраивают. Мультиплексирование также породило новые схемы отказа в обслуживании. В 2023 году атака Rapid Reset (CVE-2023-44487) открывала и сразу отменяла огромное количество stream, так что серверы выполняли работу по запросам, которые не учитывались в лимитах параллельности; это дало рекордные по мощности флуды. HPACK и QPACK требуют лимитов на размер таблицы заголовков, чтобы не допустить исчерпания памяти. Защита в основном — обновления и разумные лимиты: держите nginx и прокси пропатченными, ограничивайте число параллельных stream (http2_max_concurrent_streams, в nginx по умолчанию 128), ограничивайте размер заголовков и позвольте CDN поглощать объёмные атаки.
09QUIC изнутри: handshake, восстановление после потерь, миграция
Пакеты QUIC зашифрованы, включая большую часть заголовка, поэтому промежуточные устройства не могут анализировать или подменять транспортные детали, и протокол остаётся способным к развитию. Номера пакетов никогда не повторяются, даже при повторной отправке, что устраняет неоднозначность TCP в том, к какой копии относится подтверждение, и делает оценки round trip точнее. Пока адрес клиента не подтверждён, сервер может отправить не больше чем в три раза больше байтов, чем получил, — это не даёт использовать QUIC для атак с усилением; под нагрузкой пакет Retry с токеном позволяет сначала подтвердить адрес. Connection ID позволяют телефону сменить сеть без нового handshake, а endpoint проверяет новый путь, прежде чем ему доверять. Цена: QUIC работает в user space, шифрование идёт на каждый пакет, а пути отправки и приёма UDP оптимизированы хуже, чем у TCP, поэтому расход CPU на байт выше. Возможности ядра вроде UDP GSO и увеличенных буферов сокетов сокращают разрыв.
10Обнаружение, fallback и реальная картина
Браузер не может заранее знать, что сервер умеет HTTP/3. Обычный путь такой: первый визит идёт по HTTP/2 поверх TCP, в ответе есть Alt-Svc: h3=":443", и следующие соединения устраивают гонку QUIC против TCP, используя то, что сработало. DNS-запись HTTPS может объявить h3 ещё до первого соединения, пропуская этот шаг. Если UDP 443 заблокирован или ограничен файрволом, браузер тихо откатывается на TCP и может какое-то время помнить о неудаче, поэтому неправильно настроенный сервер редко выглядит сломанным — он просто никогда не получает HTTP/3-трафик. На сервере проверьте, что файрвол и облачные security group разрешают UDP 443, что на обоих listener отдаётся один и тот же сертификат и что балансировщики пробрасывают UDP. В логах записывайте протокол каждого запроса ($server_protocol в nginx), чтобы видеть реальную долю каждой версии.
# Ask for HTTP/3 directly (curl built with HTTP/3 support)
curl -sI --http3-only https://example.com/ | head -1
# Is h3 advertised?
curl -sI https://example.com/ | grep -i alt-svc
dig example.com HTTPS +short
# nginx log format: add the protocol per request
# log_format main '$remote_addr "$request" $status $server_protocol';
Реальные ситуации
HTTP/3 включён, но браузеры его не используют
Проблема
В конфиге nginx есть quic listener и http3 on, nginx стартует без ошибок. DevTools по-прежнему показывает h2 для каждого запроса, даже после нескольких перезагрузок.
Как решить
Пройдите путь обнаружения. Проверьте через curl -sI, что в ответах есть Alt-Svc; add_header внутри блока location может переопределить заголовки уровня server, так что убедитесь, что он есть на реальных страницах. Убедитесь, что UDP 443 открыт в ufw и в файрволе облачного провайдера, и протестируйте снаружи через curl --http3-only. Проверьте, что nginx -V показывает модуль QUIC и что сборка OpenSSL поддерживает QUIC. Браузеры какое-то время кэшируют отсутствие HTTP/3, поэтому тестируйте в новом профиле. Если перед сервером стоит CDN, HTTP/3 — это настройка CDN, а не nginx.
Мобильные пользователи в слабых сетях жалуются на медленную загрузку
Проблема
Аналитика показывает, что у пользователей мобильных сетей в сельской местности время загрузки намного хуже, чем у пользователей Wi-Fi, с частыми зависаниями на страницах с большим количеством изображений.
Как решить
Потеря пакетов в таких сетях сильно бьёт по HTTP/2 поверх TCP, потому что один потерянный пакет останавливает все stream. Отдавайте посетителям HTTP/3 — через CDN или включив его в nginx с открытым UDP 443. Затем уменьшите то, что нужно каждой странице: сожмите изображения в AVIF или WebP с адаптивными размерами, сделайте lazy-load для всего ниже первого экрана и дайте приоритет LCP-изображению. Меньше origin — меньше handshake. Сравнивайте метрики реальных пользователей по протоколу и типу соединения до и после, а не лабораторные тесты на быстром Wi-Fi.
nginx не стартует после добавления HTTP/3
Проблема
После добавления listen 443 quic reuseport в каждый виртуальный хост nginx -t сообщает о дублирующейся опции listen, и reload падает.
Как решить
reuseport может встречаться только один раз для каждой пары адрес–порт во всей конфигурации. Оставьте listen 443 quic reuseport в одном блоке server, часто в default server, а в остальных используйте просто listen 443 quic. Запускайте nginx -t перед каждым reload. Заодно убедитесь, что в каждом блоке server, который должен предлагать HTTP/3, есть http3 on и отправляется Alt-Svc, а ssl_protocols включает TLSv1.3, который нужен QUIC.
Шпаргалка
- Какой протокол использует сайт?
curl -sI -o /dev/null -w "%{http_version}\n" https://example.com/- Принудительно HTTP/2
curl -sI --http2 https://example.com/ | head -1- Только HTTP/3
curl -sI --http3-only https://example.com/ | head -1- Проверить согласование ALPN
openssl s_client -connect example.com:443 -servername example.com -alpn h2 </dev/null 2>/dev/null | grep -i alpn- Включить HTTP/2 в nginx 1.25.1+
http2 on;- HTTP/3 listener в nginx
listen 443 quic reuseport; http3 on;- Объявить о поддержке HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400' always;- Открыть UDP 443
sudo ufw allow 443/udp- Проверить поддержку QUIC в nginx
nginx -V 2>&1 | tr ' ' '\n' | grep -i v3
В моих реальных проектах
- На продакшен-серверах, которыми я управляю, nginx работает веб-сервером и reverse proxy, и HTTP/2 поверх TLS входит в стандартную настройку.
- Cloudflare перед моими сайтами отдаёт посетителям HTTP/2 и HTTP/3, а origin остаётся на стандартном HTTPS.
- Части моих продуктов, работающие в реальном времени (socket.io, Pusher), используют долгоживущие соединения через тот же слой nginx.
Вопросы и ответы
Q1В чём основные различия между HTTP/1.1, HTTP/2 и HTTP/3?
HTTP/1.1 текстовый и обрабатывает по одному запросу за раз на соединение, поэтому браузеры открывают несколько соединений, а медленные ответы блокируют остальные. HTTP/2 использует бинарные фреймы и мультиплексирует множество потоков по одному TCP-соединению со сжатием заголовков HPACK, устраняя head-of-line blocking на уровне HTTP. HTTP/3 сохраняет ту же идею, но работает поверх QUIC на UDP: потоки независимы на транспортном уровне, TLS 1.3 встроен, поэтому установка соединения требует меньше круговых путей, а соединения переживают смену сети. Семантика — методы, статус-коды и заголовки — во всех трёх одинакова.
Q2Что такое head-of-line blocking?
Это ситуация, когда один задержавшийся элемент держит всё, что стоит за ним в очереди. В HTTP/1.1 это происходит на уровне приложения: ответы на соединении приходят по порядку, поэтому медленный блокирует остальные. HTTP/2 решает это мультиплексированными потоками, но TCP по-прежнему доставляет байты строго по порядку, и один потерянный пакет останавливает все потоки до повторной передачи. Это head-of-line blocking на транспортном уровне, и он особенно мешает в мобильных сетях с потерями. HTTP/3 решает проблему с помощью QUIC, где каждый поток доставляется независимо, поэтому потеря задерживает только тот поток, к которому относится пакет.
Q3Почему HTTP/3 построен на UDP, а не на TCP?
TCP реализован в ядрах операционных систем, и его повсюду инспектируют промежуточные устройства, поэтому изменение его поведения занимает много лет. UDP — тонкий слой, который проходит через большинство сетей, поэтому QUIC может реализовать надёжность, управление перегрузкой, потоки и шифрование в пользовательском пространстве и быстро развиваться. Опора на UDP также делает возможными независимые потоки, избавляя от проблемы упорядоченной доставки TCP, и позволяет встроить TLS 1.3 в транспортное рукопожатие. Connection ID заменяют связку IP и порта, поэтому соединения переживают смену сети. Цена — больше нагрузки на CPU и необходимость fallback, когда UDP заблокирован.
Q4Нужен ли HTTP/3 на origin, если вы используете Cloudflare?
Нет. Cloudflare завершает соединение посетителя на своём edge, где говорит с браузером по HTTP/3 или HTTP/2. Соединение от Cloudflare до вашего origin — отдельный участок поверх TCP по HTTP/1.1 или HTTP/2, обычно по тёплым переиспользуемым соединениям в хорошо связанных сетях, где преимущества HTTP/3 невелики. Поэтому на origin я слежу, чтобы TLS был корректным для Full (strict) и был включён HTTP/2, а HTTP/3 оставляю edge. HTTP/3 на собственном nginx важен, когда посетители подключаются напрямую, без CDN.
Q5От каких старых оптимизаций производительности стоит отказаться с HTTP/2?
Domain sharding — распределение ресурсов по нескольким hostname'ам ради большего числа параллельных соединений — теперь вредит, потому что каждому hostname нужен свой DNS-запрос, TCP- и TLS-рукопожатие, и это ломает мультиплексирование и приоритизацию. Спрайты изображений и агрессивная склейка в один огромный бандл менее полезны, потому что много запросов — это дёшево, а гигантский файл плохо кэшируется: любое изменение инвалидирует всё. Встраивание множества ресурсов в HTML имеет похожую цену для кэширования. Вместо этого я делю код по маршрутам, даю ресурсам хэшированные имена с долгим временем кэширования и держу число origin'ов небольшим.
Вопросы уровня senior
S1Может ли HTTP/2 сделать сайт медленнее, чем HTTP/1.1?
Да, в определённых условиях. В сетях с потерями одно TCP-соединение, через которое идёт всё, страдает при потере пакета, тогда как шесть соединений HTTP/1.1 распределяют риск и имеют шесть окон перегрузки. Сломанная приоритизация, например прокси, игнорирующий приоритеты, может позволить большому изображению или скрипту занять соединение раньше CSS. Оставшиеся трюки эпохи HTTP/1.1 вроде domain sharding заставляют открывать лишние соединения и сводят на нет мультиплексирование. А очень маленькие начальные окна flow control могут ограничивать пропускную способность на длинных быстрых каналах. На практике HTTP/2 для типичных сайтов быстрее, но эти случаи объясняют странные регрессии. HTTP/3 решает проблему потерь, а правильные приоритеты и меньшее число origin решают большую часть остального.
S2Какие эксплуатационные затраты влечёт самостоятельный запуск HTTP/3?
Вы добавляете UDP listener, поэтому файрволы, облачные security group и балансировщики должны разрешать и пробрасывать UDP 443, а многие managed-балансировщики обрабатывают его иначе, чем TCP. Расход CPU на байт выше, чем у TCP с аппаратной разгрузкой в ядре, что важно при большом трафике. Отлаживать сложнее, потому что пакеты зашифрованы: tcpdump показывает мало, и приходится опираться на файлы qlog, логи сервера и инструменты браузера. Нужно тестировать fallback, ведь некоторые сети блокируют UDP. Поддержка в nginx требует TLS-библиотеки с поддержкой QUIC и аккуратной настройки reuseport. Для большинства команд прагматичный выбор — пусть CDN говорит с пользователями по HTTP/3, а на origin остаётся HTTP/2, если только трафик не идёт прямо на ваши серверы.
S3Как gRPC и WebSocket связаны с версиями HTTP?
gRPC определён поверх HTTP/2: каждый вызов — это stream, сообщения — Protocol Buffers с префиксом длины, а статус передаётся в trailers. Поэтому браузеры не могут использовать его напрямую и им нужен gRPC-Web или прокси, и поэтому каждый прокси на пути должен поддерживать HTTP/2 на всём протяжении, включая trailers. WebSocket начинались как HTTP/1.1 Upgrade handshake; RFC 8441 добавил WebSocket поверх HTTP/2 через extended CONNECT, а RFC 9220 делает то же для HTTP/3, хотя многие серверы и прокси до сих пор используют upgrade из HTTP/1.1. Для nginx это значит проксировать WebSocket с заголовками Upgrade и Connection и длинными таймаутами. WebTransport — родной для HTTP/3 вариант для потоков и датаграмм с низкой задержкой.
Глоссарий
- Мультиплексирование
- Одновременная отправка множества запросов и ответов по одному соединению, перемешанных в виде фреймов.
- Stream
- Один обмен запрос–ответ внутри соединения HTTP/2 или HTTP/3 со своим ID.
- Head-of-line blocking
- Один задержанный элемент тормозит всё, что стоит за ним в очереди, на уровне HTTP или транспорта.
- HPACK / QPACK
- Схемы сжатия заголовков для HTTP/2 и HTTP/3, позволяющие не пересылать повторяющиеся заголовки.
- ALPN
- Application-Layer Protocol Negotiation: расширение TLS, через которое клиент и сервер договариваются о h2 или http/1.1.
- QUIC
- Транспорт поверх UDP со встроенным TLS 1.3, независимыми stream и миграцией соединений; на нём работает HTTP/3.
- Alt-Svc
- Заголовок ответа, сообщающий браузеру, что тот же сайт доступен по другому протоколу, например h3.
- 0-RTT
- Отправка данных в самом первом пакете возобновлённого соединения; быстро, но уязвимо к повтору, поэтому только для безопасных запросов.
- Connection ID
- Идентификатор соединения в QUIC, не зависящий от IP и порта; позволяет менять сеть без переподключения.
- Flow control
- Объявленный получателем лимит на то, сколько данных отправитель может послать, прежде чем ждать.
Что изучить дальше
Есть идея продукта?Давайте создадим его.
Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.