MAATRIX / Блог / Почему HTTP/3 выбросил TCP и что это меняет для вашего сервера

Почему HTTP/3 выбросил TCP и что это меняет для вашего сервера

MAATRIX

Вы открываете конфиг веб-сервера, видите строку listen 443 quic reuseport и задаётесь резонным вопросом: зачем вообще ломать то, что тридцать лет работало на TCP? HTTP/3 не просто добавил новую версию протокола поверх старого транспорта — он сменил сам фундамент. Разберём, что не устраивало разработчиков в TCP, почему решением стал переход на UDP и что это значит для настройки firewall и портов на вашем сервере.

TCP и головная боль, которую HTTP/2 не вылечил

HTTP/1.1 и HTTP/2 работают поверх TCP: гарантированная доставка, порядок байтов и контроль перегрузки — то, без чего передавать страницу по ненадёжной сети было бы мучением. HTTP/2 добавил поверх TCP мультиплексирование: вместо TCP-соединения на каждый файл браузер и сервер гоняют десятки логических потоков (streams) внутри одного соединения — CSS, JS, картинки едут параллельно по одному каналу.

Проблема в том, что для самого TCP этих потоков не существует. TCP видит один непрерывный поток байтов и не знает, что внутри закодировано двадцать разных HTTP-потоков. Когда где-то на маршруте теряется сегмент, TCP обязан восстановить исходный порядок байтов, прежде чем отдать хоть что-то приложению выше, — а значит, все данные, пришедшие позже потерянного сегмента, зависают в буфере ядра, даже если физически принадлежат совсем другому, благополучно доехавшему потоку. Это и есть head-of-line blocking на транспортном уровне: одна потеря блокирует все потоки в соединении, хотя логически друг от друга они не зависят.

HTTP/2 решил проблему на уровне HTTP-семантики — придумал сами потоки внутри соединения. Но транспорт под ним остался прежним TCP, который об этих потоках ничего не знает: приложение мультиплексирует красиво, а снизу всё равно сериализуется в один байтовый поток и тормозит целиком при любой потере. Именно эта нестыковка между уровнями и стала главным аргументом не чинить TCP очередной надстройкой, а построить новый транспорт, где потоки существуют не только в голове у HTTP, но и в голове у транспортного протокола.

QUIC: потоки существуют на транспортном уровне

QUIC — новый транспортный протокол, на котором строится HTTP/3. Ключевое решение: вместо очередной надстройки над TCP взяли UDP как минимальный, ничего не гарантирующий фундамент, и реализовали поверх него собственный транспорт с нуля — с учётом всего, что было выучено за десятилетия жизни с TCP. Что именно UDP даёт «бесплатно», а что снимает с себя, подробно разобрано в статье почему UDP «быстрее» TCP и чем вы за это платите — в QUIC как раз и происходит основная часть этой «платы».

Главное отличие от связки TCP + HTTP/2: в QUIC поток (stream) — понятие транспортного уровня, а не то, что HTTP домысливает поверх байтового потока. Каждый QUIC-пакет несёт кадры (frames), явно привязанные к идентификатору потока и смещению внутри него. Подтверждения и повторная отправка тоже считаются в терминах конкретного потока, а не единого сквозного номера байта на всё соединение, как у TCP. Отсюда независимая обработка потерь: если пакет потока № 3 потерялся, QUIC-стек продолжает без задержки отдавать приложению уже пришедшие данные потоков № 1, 2, 4 — им нет причины ждать. Ждёт и пересобирается только тот поток, чей пакет потерялся.

Оговорка: независимость потоков не значит, что потеря вообще ничего не стоит. Поток с потерянным пакетом всё равно ждёт повтора и отстаёт — просто это отставание больше не утягивает за собой остальные потоки. На канале с редкими случайными потерями (а не полным обрывом связи) это заметно меняет поведение сайта: одна подтёкшая картинка не тормозит загрузку HTML и критичного JS рядом с ней.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Шифрование не поверх, а внутри транспорта

В классическом стеке TCP и TLS — независимые слои: TCP ничего не шифрует, а TLS запускается уже поверх готового TCP-соединения как отдельная надстройка, которую можно и не подключать (обычный HTTP без S). QUIC устроен иначе: шифрование — неотъемлемая часть транспорта, а не опциональная прослойка сверху. QUIC обязательно использует TLS 1.3 для согласования ключей, и почти всё содержимое пакета — включая служебные транспортные данные, а не только полезную нагрузку HTTP — шифруется и защищено от подмены. Открытым остаётся только минимальный набор полей заголовка, нужных для маршрутизации пакета до конкретного QUIC-соединения на сервере.

Практическое следствие: «HTTP/3 без шифрования» не существует как вариант — в отличие от TCP, где HTTP без TLS десятилетиями жил и до сих пор живёт. Раз транспорт переписывают с нуля, шифрование по умолчанию встраивают сразу, а не оставляют на усмотрение того, подключат TLS сверху или нет. Заодно закрывается класс атак на подмену транспортных метаданных посредником: раньше они шли открытым текстом на уровне TCP, теперь подделать их без ключа нельзя. Механику самого TLS-рукопожатия — что стороны говорят друг другу до первого байта данных — разбирали в статье про TLS-рукопожатие; в QUIC используются те же принципы TLS 1.3, но переговоры идут не поверх готового транспортного соединения, а одновременно с его установлением.

Быстрее на старте: одно рукопожатие вместо двух подряд

В классической связке TCP + TLS 1.3 установление защищённого соединения — это два разговора подряд. Сначала TCP-рукопожатие (SYN, SYN-ACK, ACK), и только когда транспорт готов, поверх него стартует TLS-рукопожатие. Оба требуют минимум по одному полному обороту пакетов туда-обратно, и второе не может начаться, пока не завершится первое, — они последовательны по конструкции, потому что TLS ничего не знает про TCP и просто ждёт готовый канал на вход.

QUIC объединяет эти два разговора в один. Установление транспортного соединения и согласование TLS-параметров происходят в одном обмене пакетами: первый же пакет клиента уже несёт и транспортные параметры QUIC, и ClientHello с ключевым материалом TLS 1.3. Серверу не нужно сначала поднимать транспорт, а потом отдельно начинать TLS — оба процесса идут параллельно в одной последовательности сообщений. Итог — на новое соединение уходит на один полный оборот пакетов туда-обратно меньше, чем в схеме TCP + TLS поверх него, просто за счёт того, что раньше строго последовательные шаги теперь идут одновременно.

Для повторного подключения к серверу, с которым клиент уже общался и сохранил параметры сессии, QUIC поддерживает 0-RTT — отправку ранних данных приложения в самом первом пакете, ещё до завершения рукопожатия. Тут стоит быть честным: 0-RTT — не бесплатный обед. Данные, отправленные в 0-RTT, потенциально уязвимы к replay-атаке (перехвативший такой пакет может повторно отправить его серверу), поэтому 0-RTT принято использовать только для идемпотентных запросов, а сервер сам обязан защищаться от повторов, если вообще его разрешает. Многие реализации по умолчанию используют его ограниченно.

Что QUIC пришлось построить заново поверх «голого» UDP

Здесь и кроется главная плата за отказ от TCP. Сам по себе UDP не даёт ничего из того, что раньше было бесплатным приложением к TCP: ни гарантированной доставки, ни порядка, ни контроля перегрузки. Всё это QUIC обязан реализовать заново, в пользовательском пространстве, своими силами.

  • Надёжность и повторная отправка. QUIC нумерует каждый пакет собственным монотонно растущим номером и явно подтверждает диапазоны полученных пакетов ACK-кадрами. Это устроено осторожнее классического TCP ACK: TCP годами страдал от неоднозначности между «оригинал пришёл с задержкой» и «пришёл повтор» при ретрансмиссии, что портило точность измерения RTT. У QUIC номер пакета никогда не переиспользуется даже при повторной отправке тех же данных — неоднозначность снята в принципе.
  • Порядок доставки там, где он нужен, а не везде подряд. QUIC восстанавливает порядок байтов внутри каждого потока отдельно, а не для всего соединения разом — прямое следствие того, что потоки существуют на транспортном уровне.
  • Контроль перегрузки сети. QUIC сам следит, не перегружает ли он канал, как годами делал TCP через slow start и оценку RTT. Разница в том, где живёт эта логика: у TCP она в ядре ОС, и обновление алгоритма требует обновлять ядра на серверах и клиентах по всему миру. У QUIC контроль перегрузки — в пользовательском пространстве, внутри библиотеки, а значит обновляется вместе с приложением, не дожидаясь миграции ядра у миллионов пользователей.
  • Миграция соединений. QUIC привязывает сессию к идентификатору соединения (connection ID), а не к паре IP-адресов и портов, как TCP. Сменился адрес клиента — телефон переключился с Wi-Fi на мобильную сеть — TCP-соединение в этот момент обязано порваться и открыться заново. QUIC-соединение в такой ситуации может продолжиться на новом адресе, потому что сервер узнаёт клиента по connection ID.

Стоит признать: переизобретение этих механизмов с нуля не бесплатно. Обработка и шифрование пакетов в пользовательском пространстве вместо оптимизированного десятилетиями кода в ядре ОС требует больше процессорного времени на сервере, чем классическая связка TCP + TLS. Насколько заметно — сильно зависит от реализации и профиля трафика; конкретных цифр здесь намеренно не будет, они устареют или не совпадут с вашим случаем. Ориентируйтесь на собственные замеры CPU до и после включения HTTP/3 под реальной нагрузкой.

Что это значит на практике при настройке сервера

Самое частое недоразумение: включили HTTP/3 в конфиге, а он не работает, хотя раньше через тот же порт 443 всё прекрасно ходило. Причина почти всегда одна: 443 в firewall открыт только по TCP, а QUIC ходит по UDP на том же номере порта — это отдельный, никак не связанный с TCP-443 канал с точки зрения сетевого стека и firewall-правил.

Проверить, слушает ли сервер QUIC на UDP, можно так:

ss -ulnp | grep :443

Если строки нет — либо сервер не поднял QUIC (нет модуля или сборки), либо порт занят чем-то другим. Правило firewall для UDP нужно добавлять отдельно от TCP-правила:

# UFW
ufw allow 443/udp

# iptables
iptables -A INPUT -p udp --dport 443 -j ACCEPT

Тонкость, о которой стоит знать заранее: у stateful firewall и NAT-таблиц UDP ведёт себя иначе, чем TCP. У TCP есть явный флаг FIN/RST, по которому firewall понимает, что соединение закрыто, и убирает запись из таблицы состояний. У UDP такого сигнала нет — это датаграммы без понятия «соединение» на уровне протокола. Firewall и NAT угадывают завершение QUIC-«сессии» по таймауту бездействия, и если он выставлен слишком коротким (дефолты для UDP на многих устройствах исторически жёстче, чем для TCP — сказывается прошлое UDP как транспорта коротких DNS-запросов), долгоживущее QUIC-соединение может внезапно перестать пропускаться, хотя порт формально открыт.

Ещё момент: часть корпоративных сетей и провайдеров блокирует или жёстко ограничивает UDP, потому что исторически на него смотрели как на источник DDoS-амплификации, а не как на транспорт веб-трафика. HTTP/3 спроектирован с расчётом на такую реальность: сервер отдаёт заголовок Alt-Svc, сообщающий, что сервис доступен по HTTP/3 на таком-то порту, но если QUIC не удался или UDP не долетает, браузер молча откатывается на HTTP/2 поверх обычного TCP-соединения. Для пользователя это незаметно — сайт просто открывается, возможно, чуть медленнее. Отсюда практический вывод: включайте HTTP/3 как опциональное ускорение поверх работающего HTTP/2, оставляя TCP-путь полностью рабочим, а не вместо него.

Если после открытия UDP-порта HTTP/3 всё равно не подхватывается браузером или curl, разбор конкретных типовых причин — от отсутствия модуля сборки до неверного заголовка Alt-Svc — есть в статье HTTP/3 и QUIC на сервере: частые ошибки и решения, а пошаговая установка с нуля — в статье как установить и настроить HTTP/3 и QUIC на VPS.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Нужно ли закрывать TCP-порт 443, если я включил HTTP/3 по UDP?

Нет. HTTP/3 — опциональное ускорение, а не замена HTTP/2. Держите TCP-443 открытым как основной канал, а UDP-443 добавляйте рядом. Если UDP по какой-то причине не доедет до клиента, браузер откатится на HTTP/2 по TCP, и сайт продолжит работать.

Что будет, если провайдер или корпоративный firewall клиента блокирует UDP целиком?

Ничего критичного для пользователя. Браузер, не сумев установить QUIC-соединение за разумное время, автоматически перейдёт на HTTP/2 поверх TCP — это штатный механизм согласования, а не поломка. Пользователь потеряет прирост скорости от HTTP/3, но не доступ к сайту.

Как проверить, что сайт действительно отдаёт HTTP/3, а не только заявляет о поддержке?

Откройте вкладку Network в DevTools и добавьте колонку Protocol — там будет видно h3 для запросов, реально прошедших по QUIC. Со стороны сервера ss -ulnp | grep :443 покажет, слушает ли процесс UDP-порт, а curl с флагом --http3 (если собран с поддержкой) явно запросит HTTP/3 и покажет ошибку, если соединение не удалось.

HTTP/3 всегда быстрее HTTP/2?

Не всегда и не для всех сценариев. На стабильном канале с низкими потерями пакетов выигрыш от независимой обработки потоков малозаметен, а объединённое рукопожатие экономит время в основном на новых соединениях, а не на уже прогретых keep-alive. Разница ощутимее там, где есть потери пакетов, высокий пинг или частая смена сети, — типичный профиль мобильного трафика.

Можно ли использовать QUIC без шифрования, например для внутреннего служебного трафика между своими серверами?

Нет, спецификация не предусматривает такого режима — TLS 1.3 встроен в транспорт QUIC обязательно, отдельного незашифрованного варианта протокол не определяет. Для внутренней сети незашифрованный быстрый транспорт — отдельная задача, для которой QUIC не проектировался.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →