1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 2 из 5

Включаем HTTP/3

Коротко

HTTP/3 добавляют вторым listen рядом с обычным - это два разных сокета, UDP и TCP, и нужны оба.

  • listen 443 quic reuseport; плюс listen 443 ssl;. Сертификат обязателен и для QUIC: без него no "ssl_certificate" is defined for the "listen ... quic".
  • reuseport пишется один раз на порт, иначе duplicate listen options.
  • Без add_header Alt-Svc браузеры про QUIC не узнают, без открытого UDP в файрволе он не заработает молча, без единой ошибки в логах.
  • Проверять - переменной $http3 в логе. curl --http3 без -only умеет тихо откатиться на TCP и соврать.

Если знаешь про два listen, Alt-Svc и UDP в файрволе - листай до «Теперь сам».

Сначала ответь сам

Конфиг написан по статье, сертификат на месте, nginx -t доволен, порт слушает. Браузер упорно ходит по HTTP/2, в логах ошибок нет. Что забыли?

Вариантов ровно три, и все три - вне блока server, где ты ищешь: не открыт UDP в файрволе, не отдаётся Alt-Svc или заголовок съело наследование в location. Ни один из них не даёт ни строчки в error_log: HTTP/3 не «ломается», он просто не используется.

На что это похоже

Второй вход в здание, о котором никому не сказали. Дверь есть, ключи есть, охрана на месте - а люди ходят через главный, потому что о втором написано на табличке внутри здания, куда они попадают только через главный.

Табличка - это Alt-Svc. Пока её не увидели по TCP, в UDP никто не постучится.

Механизм: два сокета на одном номере порта

listen 443 ssl TCP: HTTP/1.1 и HTTP/2 listen 443 quic UDP: HTTP/3 первое соединение всегда сюда и отсюда приходит Alt-Svc сюда клиент придёт вторым разом, если UDP не режут убери TCP-строку - и клиенты без HTTP/3 потеряют сайт совсем
Номер порта один, сокета два. Они не конфликтуют и нужны оба.

Правило

/etc/nginx/conf.d/shop.local.confhttp
server {
    listen 443 quic reuseport;      # UDP - для HTTP/3
    listen 443 ssl;                 # TCP - для HTTP/2 и HTTP/1.1
    http2 on;

    server_name shop.local;
    ssl_certificate     /etc/letsencrypt/live/shop.local/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/shop.local/privkey.pem;

    add_header Alt-Svc 'h3=":443"; ma=86400' always;

    root /var/www/shop;
}

Директиву http3 писать не нужно - она on по умолчанию (в отличие от http2). А сертификат для QUIC обязателен ровно так же, как для ssl:

↳  выводnginx -t
nginx: [emerg] no "ssl_certificate" is defined for the "listen ... quic" directive in /etc/nginx/conf.d/shop.local.conf:2
nginx: configuration file /etc/nginx/nginx.conf test failed

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

↳  выводnginx -t
nginx: [emerg] duplicate listen options for 0.0.0.0:443 in /etc/nginx/conf.d/shop.local.conf:2
nginx: configuration file /etc/nginx/nginx.conf test failed

Сайтов на порту несколько - reuseport пишется в одном из них, любом.

Разбор: Alt-Svc и его цена

/etc/nginx/conf.d/shop.local.confhttp server
add_header Alt-Svc 'h3=":443"; ma=86400' always;

h3=":443" - на каком порту слушает HTTP/3 (пустой хост означает «тот же»), ma - на сколько секунд запомнить. Флаг always нужен, чтобы анонс уезжал и с ответами 404: иначе клиент, чей первый запрос не попал в 200, останется на TCP до следующего раза.

Заголовок ставится на уровне server, и правило наследования из второго раздела никуда не делось: собственный add_header в каком-нибудь location /api/ отбросит Alt-Svc вместе с остальными. Лечится директивой add_header_inherit merge на уровне http (разбор - в разделе про наследование).

ma=86400 означает, что браузер сутки будет считать сайт умеющим HTTP/3. Если после выключения QUIC клиенты жалуются на медленный первый запрос - это они безуспешно стучатся по UDP и ждут таймаута. Перед экспериментами разумно взять ma=300, а на сутки переходить, когда всё устоялось.

Разбор: как убедиться, что он работает

Самый честный способ - переменная $http3 в логе: она равна h3 только для соединений, реально пришедших по QUIC.

/etc/nginx/nginx.confhttp
log_format quic '$remote_addr "$request" $status proto=$http3 $server_protocol';

Со стороны клиента - curl со сборкой, умеющей QUIC. И тут ловушка:

↳  выводcurl --http3 https://shop.local/
proto=HTTP/2.0 quic=
↳  выводcurl --http3-only https://shop.local/
proto=HTTP/3.0 quic=h3

Флаг --http3 разрешает попробовать QUIC и молча откатывается на TCP, если что-то не вышло. Проверять надо --http3-only: он либо соединится по HTTP/3, либо честно упадёт с ошибкой. В браузере смотри колонку Protocol во вкладке Network - и помни, что первый запрос всегда придёт по TCP.

Что ломается без этого

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

$  команда
ufw allow 443/udp

ufw - частый фронтенд к файрволу в Ubuntu/Debian; эта строка открывает UDP-порт 443. То же самое проверь в облачной security group и на пограничном роутере. И учти: если перед nginx стоит L4-балансировщик, он тоже должен уметь и пробрасывать UDP.

Убранная TCP-строка. Оставить один listen 443 quic - значит выключить сайт для всех, кто не умеет HTTP/3 или чей провайдер режет UDP. И заодно лишить браузеры возможности узнать про QUIC: Alt-Svc приходит по TCP.

Зачем это в работе

Четыре директивы, которые стоит знать после включения:

quic_retry on - валидация адреса клиента: сервер отвечает пакетом Retry и продолжает, только если клиент его вернул. На публичных сайтах включать стоит: без этого QUIC-сервер удобно использовать как усилитель в атаках с подменой адреса отправителя.

quic_host_key file - ключ, которым шифруются токены проверки адреса и stateless reset. Без него ключ генерируется случайно при каждом старте, и токены перестают действовать после reload; за балансировщиком из нескольких серверов файл должен быть общим.

quic_gso on - отправка пачками через UDP-сегментацию, заметно снимает нагрузку на процессор. Только Linux с поддержкой UDP_SEGMENT.

quic_bpf on (контекст main, Linux 5.7+) - маршрутизация пакетов по Connection ID силами ядра. Именно она позволяет мигрирующему соединению оставаться на своём рабочем процессе.

Про 0-RTT отдельно: ssl_early_data on разрешает клиенту прислать запрос вместе с первым пакетом, но такой запрос можно записать и воспроизвести повторно. Для GET каталога это безразлично, для «спиши деньги» - нет. Включать только если приложение защищено от повторов; работает начиная с nginx 1.29.1 и OpenSSL 3.5.1.

Теперь сам

HTTP/3 включили, curl --http3-only отвечает HTTP/3 200, в логе появились строки с proto=h3. Через неделю доля h3 в логах - полпроцента, хотя браузеры у клиентов свежие. Где искать?

Смотри на Alt-Svc в ответах разных маршрутов: скорее всего, на главной он есть, а на том пути, с которого начинается сессия пользователя, его съел собственный add_header в location. Проверяется одной командой на каждый подозрительный путь:

$  команда
curl -sI https://shop.local/api/config | grep -i alt-svc

Второй кандидат - UDP, открытый на сервере, но зарезанный где-то по пути: облачной security group или корпоративной сетью клиентов. Первый случай чинится, второй - нет, и это нормально: HTTP/3 всегда дополнение к HTTP/2.

Главное

HTTP/3 добавляют вторым listen 443 quic reuseport рядом с listen 443 ssl - это два сокета, UDP и TCP, и нужны оба. Сертификат обязателен и для QUIC, reuseport пишется один раз на порт, http3 уже включён по умолчанию. Без add_header Alt-Svc браузеры про QUIC не узнают, а без открытого UDP в файрволе он не заработает молча, без единой строки в логах. Проверять - переменной $http3 в логе и curl --http3-only: обычный --http3 тихо откатывается на TCP и врёт.

Комментарии

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

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

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