Как HTTP/1.1, CGNAT и ТСПУ однажды собрались вместе и сказали — включи HTTP/2

Был такой баг, который висел в бэклоге несколько недель. У части пользователей сайт то грузится, то нет. Иногда открывается голый HTML без стилей и картинок. Иногда просто ERR_CONNECTION_TIMED_OUT. У одних работает всегда, у других — никогда, у третьих — через раз. VPN помогает. Или не помогает — зависит от VPN.

Классика: симптомы плавающие, воспроизведения нет, на сервере всё чисто. В итоге проблему повесили на ТСПУ и забыли. Мало ли что там они опять накрутили.

Вчера я тестировал новые фичи, и у меня на айфоне сайт перестал открываться полностью. Все способы прошли — Safari, Chrome, Firefox на iOS. С того же вайфая ноутбук заходит без вопросов. Мобильный интернет на том же айфоне — тоже работает. То есть проблема именно в связке «конкретный вайфай + конкретное устройство». Воспроизведение есть — значит, можно копать.

Дальше оказалось, что сервер и приложение тут вообще ни при чём. Проблема в том, что три независимые вещи сложились в одну неприятную сумму.

Как вообще браузер открывает сайт

Когда вы открываете страницу, браузер не делает один запрос — он делает десятки. Сам HTML, потом CSS, потом шрифты, картинки, JS-бандлы, фавикон, аналитика. Для каждого ресурса нужно соединение с сервером.

По HTTP/1.1 одно TCP-соединение в каждый момент времени обслуживает один запрос. Пока сервер не ответил — по этой же сессии ничего больше не отправить. Чтобы грузить параллельно, браузер открывает несколько соединений.

Сколько именно? В RFC 2616 (это старый стандарт HTTP/1.1) рекомендовано не больше двух на сервер. Но разработчики браузеров на это правило плюнули ещё давно — Opera и Safari первыми подняли до 4, а в 2008 году IE8, Firefox 3 и Chrome перешли на 6 соединений на домен. С тех пор так и живём: шесть параллельных TCP-сессий на один домен — хардкод в современных браузерах.

Шесть — это только базовое значение. Сверху накладывается то, что делают современные движки: preconnect, preload, speculative connections. Браузер заранее открывает сокеты под ресурсы, которые он ещё даже не решил запрашивать — на случай если вдруг понадобится. В моменте при загрузке сайта реально держится 10–15 активных TCP-соединений от одного устройства к одному домену.

Для обычного сервера в обычной сети это не проблема. Сокетов не жалко, RAM под них почти ничего не ест, всё работает.

Проблема начинается, когда между вами и сервером стоит что-то, что считает сессии.

CGNAT и почему у вас давно нет белого IP

В последние пару лет многие провайдеры тихо перевели абонентов на CGNAT (carrier-grade NAT). Публичных IPv4-адресов не хватает всему миру, покупать их на вторичном рынке дорого — сейчас адреса торгуются по 30–50 долларов за штуку. Проще посадить тысячу абонентов на один белый IP и разруливать их NAT’ом на стороне провайдера.

Работает это так. Ваш домашний роутер получает не публичный адрес, а серый — из диапазона 100.64.0.0/10 (это официальный диапазон для CGNAT по RFC 6598). Дальше стоит здоровенная коробка провайдера, которая все ваши сессии переписывает под один общий белый IP, различая абонентов по портам.

У любого NAT есть ограничение: портов всего 65535, из них реально используется диапазон 1024–65535, то есть около 64 тысяч. Эти 64 тысячи делятся между всеми абонентами, сидящими на этом же белом IP. Типичный ISP выдаёт одному абоненту от 1000 до 4000 портов — это и есть потолок одновременных исходящих TCP-сессий с вашего устройства, пока вы под CGNAT.

В RFC 6888, где описаны требования к CGNAT, прямым текстом сказано: система обязана поддерживать лимиты портов на абонента, иначе один пользователь может выжрать все ресурсы у соседей. То есть лимит — это не баг, это by design.

Пока вы просто ходите по вебу, 1000 портов — это вроде бы много. Но это на все ваши устройства одновременно. Телефон, ноутбук, смарт-ТВ, пылесос с вайфаем, умная колонка, которая раз в секунду стучится в облако — всё это жрёт из общего пула. Плюс state в CGNAT держится минутами после закрытия соединения. Порт вы уже не используете, но он ещё «горячий» и не выдаётся другому.

ТСПУ тоже считает сессии

Отдельно от CGNAT на маршруте сидит ТСПУ — «технические средства противодействия угрозам», DPI-оборудование, которое с 2018 года стоит у каждого российского провайдера по закону. Изначально оно ставилось для блокировок, но делает гораздо больше.

ТСПУ анализирует трафик послойно — от L3 до L7. На транспортном уровне ему доступны IP, порты, TCP-флаги, номера последовательностей, размеры и частота пакетов. Для незашифрованных протоколов видна вся полезная нагрузка, для TLS — только метаданные. Чтобы всё это анализировать, оборудованию приходится держать state по каждой TCP-сессии — иначе оно не понимает, к какому соединению относится пришедший пакет.

В 2025 году ТСПУ получила апгрейд. В систему внедрили ML-алгоритмы, которые помимо сигнатурного анализа учитывают поведенческие характеристики соединений — длительность сессий, частоту подключений к одному IP, объёмы данных. Это нужно, чтобы ловить VPN-трафик, замаскированный под обычный HTTPS. Для такого анализа нужно ещё больше state-машин, ещё больше памяти на сессию.

И весь этот праздник имеет мощностные лимиты. В марте 2026 года «Коммерсантъ» сообщил, что Минцифры планирует увеличить мощность ТСПУ в 2,5 раза — как раз потому, что нынешней не хватает. Другими словами, оборудование работает на пределе. Когда оно упирается в потолок, оно не пишет «извините, я устало», оно просто начинает ронять сессии.

А теперь все вместе

Теперь соберём пазл.

Пользователь заходит на сайт по HTTP/1.1. Браузер открывает 6–15 параллельных TCP-сессий. Эти сессии проходят через домашний роутер, через CGNAT провайдера, через ТСПУ — и в каждой точке потребляют ресурс из ограниченного пула. Сессии от других ваших устройств с того же вайфая тоже идут туда же. Плюс остаточные state от уже закрытых соединений ещё висят в таблицах.

В большинстве случаев запаса хватает. Но на «плохой» комбинации — когда CGNAT загружен, на белом IP сидит много народу, телевизор в соседней комнате стримит 4K — новые сессии вашего браузера начинают не пролезать. Часть картинок и CSS грузится, часть нет. Браузер пытается переоткрыть сессию — не получается. ERR_CONNECTION_TIMED_OUT.

Сервер ни при чём. Провайдер формально ни при чём — он ничего не блокирует, он просто не может выдать вам больше портов из общего пула. ТСПУ ни при чём — она делает то, что должна, просто у неё нет ресурса на ещё одну сессию.

Почему у одних работает, а у других нет? Потому что всё зависит от того, сколько народу сидит на вашем CGNAT-IP прямо сейчас, как именно настроены лимиты у конкретного провайдера, сколько state уже держат устройства в вашей сети, и какой именно DPI-узел обрабатывает ваш трафик. В Москве один расклад, в Воронеже другой, у МТС третий, у Ростелекома четвёртый.

И почему VPN помогает — но не всегда? Когда трафик уходит в VPN, все ваши сессии к разным сайтам мультиплексируются в одно зашифрованное соединение. С точки зрения CGNAT и ТСПУ у вас одна сессия, а не 15. Но если VPN-сервер сам обрубается ТСПУ по поведению (а их сейчас обрубают всё агрессивнее) — всё снова ломается.

Что делать на стороне сайта

Включить HTTP/2. Это чинит почти всю проблему одним шагом.

В HTTP/2 браузер открывает одно TCP-соединение на домен и внутри него мультиплексирует все запросы параллельными стримами — per-domain-лимит на соединения уходит как явление. Лимит на одновременные стримы задаёт сервер через параметр SETTINGS_MAX_CONCURRENT_STREAMS, и минимум по RFC 7540 — 100. То есть вместо 15 TCP-сессий у пользователя одна, и в ней параллельно летят все те же 15 (и больше) запросов.

Для пользователя это значит: один порт в CGNAT вместо пятнадцати, одна запись в state-таблице ТСПУ вместо пятнадцати. Шанс упереться в лимит падает на порядок.

Переключение обычно простое. Если у вас nginx — добавить http2 в директиву listen и проверить, что включён TLS (HTTP/2 в браузерах работает только поверх TLS). Если Cloudflare или другой CDN — там HTTP/2 и HTTP/3 включены по умолчанию, надо просто не отключать их вручную. В curl можно проверить, какой протокол отдаёт сайт:

curl -I --http2 https://example.com -v 2>&1 | grep -i "alpn\|http/"

Если в ALPN договорились на h2 — всё хорошо. Если h1 — есть что чинить.

Что из этого следует на будущее

Если у вас российская аудитория и HTTP/1.1 — вы подложили себе бомбу с таймером. Не в том смысле, что сайт сразу ляжет. В том смысле, что у какой-то доли пользователей он будет глючить непредсказуемо, а вы никогда не увидите причину: в логах сервера ничего не будет, в аналитике — просто connection timeout от браузера, в поддержке — «у меня почему-то не работает, с телефона нормально».

Раньше, когда провайдеры выдавали белые IP и ТСПУ была помягче, это сходило с рук. Сейчас нет. CGNAT расширяется, ТСПУ наращивает state-машину, браузеры всё агрессивнее используют спекулятивные соединения. Запас прочности сжимается.

Я долго не мог понять, почему именно сейчас проблема вылезла, хотя сайт работал годами. Теория такая: по отдельности ни один из факторов не новый. А вот их совместная загруженность — новая. И дальше будет только плотнее.

Оставить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *