MAATRIX / Блог / Почему совет «переключить VPN на TCP для стабильности» вредит

Почему совет «переключить VPN на TCP для стабильности» вредит

MAATRIX

«У меня VPN рвётся и тормозит на нестабильном интернете — переключи протокол на TCP, он надёжнее» — этот совет кочует по форумам и чатам поддержки уже много лет. Звучит логично: TCP гарантирует доставку, UDP — нет, значит для плохой сети нужен TCP. На практике всё ровно наоборот: включение TCP-транспорта на нестабильной линии обычно делает VPN не стабильнее, а заметно хуже, вплоть до полного зависания при малейшей потере пакетов. Разберём, почему так происходит и когда TCP-транспорт для VPN всё же оправдан.

Откуда берётся этот совет и почему он звучит логично

Совет обычно всплывает в одной и той же ситуации: VPN подвисает, страницы грузятся рывками, RDP-сессия замирает, звонок в мессенджере через VPN рассыпается. Пользователь открывает настройки клиента — у OpenVPN, SSTP, у некоторых коммерческих VPN-приложений там буквально есть переключатель «Protocol: UDP / TCP» — и меняет UDP на TCP, потому что где-то читал: TCP надёжный протокол с подтверждением доставки и повторной отправкой потерянных пакетов, а UDP — «пальнул и забыл», без гарантий.

Логика здесь одна: раз моя сеть теряет пакеты, я хочу протокол, который эти потери компенсирует. TCP как будто для этого и создан. Проблема в том, что это рассуждение упускает главное: сам VPN — это не приложение, которое напрямую передаёт ваши данные. Это туннель, внутри которого уже едет трафик других приложений: браузера, SSH-клиента, RDP, торрент-клиента, почтового клиента. И подавляющее большинство этого трафика — уже TCP. Веб, SSH, RDP, SMTP, большинство API поверх HTTPS — всё это TCP-соединения. Когда вы переключаете транспорт VPN на TCP, вы не «добавляете надёжности» — вы заворачиваете уже надёжный TCP-поток в ещё один TCP-поток.

TCP поверх TCP: что происходит технически

Возьмём типичный сценарий: вы открываете сайт через VPN. Браузер устанавливает TCP-соединение до веб-сервера (или до прокси на удалённом конце) — с собственным номером последовательности, окном, таймером повторной передачи, оценкой RTT. Это TCP-соединение №1, «внутренний» уровень — оно живёт внутри данных, которые VPN-клиент шифрует и отправляет на сервер.

Дальше зависит от транспорта VPN:

  • UDP-транспорт (WireGuard всегда работает так, OpenVPN с proto udp, большинство современных протоколов вроде Shadowsocks и VLESS в UDP-режиме): зашифрованные данные внутреннего TCP-соединения просто кладутся в UDP-датаграммы и отправляются на сервер. У UDP нет собственной ретрансмиссии, окна, номеров последовательности — это «тупая труба», которая либо доставляет датаграмму, либо теряет её молча. Никакого второго уровня управления надёжностью не появляется.
  • TCP-транспорт (OpenVPN с proto tcp, SSTP, часть коммерческих клиентов в режиме «через 443 порт для обхода блокировок»): те же зашифрованные данные внутреннего TCP-соединения теперь передаются через ещё одно, внешнее TCP-соединение — от VPN-клиента до VPN-сервера. У этого внешнего TCP есть собственный номер последовательности, собственное окно перегрузки (congestion window), собственный таймер retransmission timeout (RTO), собственная оценка RTT.

В файле конфигурации это выглядит буквально одной строкой:

# было
proto udp
port 1194

# стало
proto tcp
port 443

Разница в одной директиве кажется безобидной. На деле она меняет архитектуру передачи данных: вместо одного уровня TCP-состояния (внутреннего, приложения) вы получаете два независимых, вложенных друг в друга уровня TCP-состояния, каждый из которых не подозревает о существовании другого.

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

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

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

Почему это резко роняет производительность при потерях пакетов

Проблема «TCP поверх TCP» (TCP-in-TCP, иногда называют TCP meltdown) — известная и хорошо документированная в сетевой литературе ситуация, которая проявляется именно тогда, когда канал теряет пакеты. На идеально стабильной линии без потерь два вложенных TCP-соединения работают почти так же, как одно — просто с лишним заголовком и небольшой задержкой. Беда начинается, когда где-то на пути реально пропадают пакеты — то есть именно в той ситуации, ради которой пользователь и переключался на TCP.

Механизм такой:

  1. Пакет теряется на физическом уровне сети (плохой Wi-Fi, перегруженная мобильная сеть, проблемный участок маршрута).
  2. Эту потерю обнаруживает внешний TCP-туннель — по таймауту или дублирующимся ACK. Он запускает свою ретрансмиссию и одновременно реагирует на потерю как на сигнал перегрузки: уменьшает окно (congestion window), может откатиться в slow start. Это занимает время — иногда сотни миллисекунд, иногда секунды, в зависимости от RTT и текущего состояния окна.
  3. Пока внешний TCP разбирается с повторной отправкой, внутреннее TCP-соединение (то есть соединение вашего браузера или SSH-клиента) видит только возросшую задержку доставки — оно не знает, что где-то внутри тоннеля идёт повторная передача. Оно интерпретирует эту задержку по-своему: как будто и его собственный пакет потерян, и запускает свою ретрансмиссию и свою реакцию на перегрузку.
  4. В результате один и тот же фрагмент данных может повторно отправляться сразу на двух уровнях, оба уровня одновременно снижают скорость (двойное схлопывание окна), а таймеры двух независимых TCP-стеков начинают конфликтовать друг с другом — RTO внешнего соединения не согласован с RTO внутреннего, поэтому один уровень может ждать, пока «отработает» другой, а затем повторно среагировать на ту же самую задержку.

На выходе — не пропорциональное потерям падение скорости, а зачастую куда более резкое: соединение может «залипать» на несколько секунд там, где при однослойном TCP или при UDP-транспорте была бы просто заметная, но управляемая просадка. Точный порог, при котором это становится критичным, зависит от RTT, размера окна и характера потерь — конкретных цифр называть не буду, у вас в вашей сети они будут свои. Но направление эффекта устойчиво воспроизводится: чем выше потери, тем хуже TCP-поверх-TCP справляется по сравнению с UDP-транспортом. Подробнее о диагностике потерь пакетов на VPN-линии — в статье про packet loss на VPN.

Почему UDP-транспорт лучше держит нестабильную сеть

С UDP-транспортом всё устроено проще и, как ни странно, надёжнее в реальных условиях. У самого туннеля нет собственного состояния ретрансмиссии — потерянная датаграмма просто теряется, без попыток её повторно отправить на уровне туннеля. Единственный, кто реагирует на потерю, — это внутреннее TCP-соединение приложения, и оно видит реальную картину сети: настоящий RTT, настоящий процент потерь на пути, без искажений от вложенного слоя ретрансмиссий. Его алгоритм управления перегрузкой (обычно CUBIC или BBR в современных системах) настроен именно на такую работу и справляется с этим штатно — примерно так же, как справлялся бы без VPN вообще, только с добавленной задержкой шифрования и инкапсуляции.

Есть и вторая причина, по которой UDP-транспорт лучше переносит нестабильность — это уже не про потери, а про смену сети. Протоколы вроде WireGuard принципиально не привязаны к «TCP-сокету» между клиентом и сервером — у них нет состояния соединения в классическом смысле, есть только криптографическая сессия поверх UDP. Поэтому переключение сети (Wi-Fi → мобильный интернет, смена IP при роуминге) не рвёт туннель: клиент просто начинает слать датаграммы с нового адреса, сервер узнаёт его по криптографическому хендшейку и продолжает сессию. TCP-туннель в такой ситуации теряет сокет целиком — старое TCP-соединение до сервера привязано к конкретной паре IP-портов, и при смене адреса VPN обычно приходится переустанавливать соединение с нуля, что на нестабильной мобильной сети происходит постоянно и ощущается как «VPN всё время рвётся» — то есть ровно то, от чего пытались уйти советом про TCP.

Разница в самой философии протоколов — по сути, ещё один частный случай того, почему UDP в целом быстрее TCP и чем за это приходится платить: UDP отдаёт контроль надёжности верхнему уровню там, где это уместнее делать один раз, а не дважды.

Практический совет, который реально помогает на нестабильной линии, — не смена транспорта, а тюнинг keepalive:

# WireGuard, в секции [Peer] клиента
PersistentKeepalive = 25
# OpenVPN, в конфиге клиента и сервера
keepalive 10 60

Более частый keepalive быстрее обнаруживает разрыв состояния (например, из-за NAT-таймаута на мобильном операторе) и быстрее восстанавливает сессию — без замены UDP на TCP и без побочных эффектов TCP-in-TCP.

Как проверить, на чём сейчас работает ваш VPN

Прежде чем что-то менять, стоит убедиться, какой транспорт используется сейчас — многие даже не знают, что уже сидят на TCP, потому что так было по умолчанию в конкретном клиенте или так когда-то настроили «для обхода блокировок» и забыли.

Для OpenVPN — посмотреть директиву proto в конфиге:

grep -i proto /etc/openvpn/server.conf
# или в клиентском .ovpn
grep -i proto client.ovpn

Проверить, какие сокеты реально слушает и держит процесс:

ss -tnp | grep openvpn   # если есть строки — работает TCP-транспорт
ss -unp | grep openvpn   # если есть строки — работает UDP-транспорт

Для WireGuard отдельного режима TCP в принципе не существует — протокол спроектирован только под UDP, и если у вас именно WireGuard, тема этой статьи вас не касается напрямую (хотя принцип «не заворачивайте TCP в TCP» актуален и для того, что вы туннелируете через него).

Для коммерческих VPN-клиентов — обычно в настройках есть явный пункт Protocol / Connection type со значениями Automatic / UDP / TCP; если стоит TCP или Automatic, а на автомате клиент откатился на TCP из-за проблем с UDP-портом, стоит проверить логи клиента на предмет, какой транспорт был выбран по факту.

Если после проверки выясняется, что нестабильность была всё это время на UDP, — не спешите переключать на TCP. Сначала стоит исключить более вероятные причины: фрагментацию из-за неверного MTU (разбирали отдельно в статье про подбор MTU для VPN) и реальные потери на маршруте, которые диагностируются через mtr или ping с разным размером пакета — это покажет, где именно на пути теряются данные, и часто проблема решается сменой сервера или маршрута, а не транспорта.

Когда TCP всё-таки оправдан и что делать вместо переключения

Совет не абсолютно вреден — у TCP-транспорта для VPN есть узкая, но реальная область применения. Разница в том, что причина для его использования — не «нестабильность канала», а блокировка или жёсткая фильтрация UDP-трафика сетью, через которую вы подключаетесь. Это принципиально другая проблема: там не потери пакетов на плохом Wi-Fi, а сознательная политика сети — корпоративный файрвол, гостиничный или гостевой Wi-Fi, ограничения оператора связи, блокировки на уровне провайдера или страны, — которая режет или сильно душит весь неопознанный UDP-трафик, оставляя проходить преимущественно TCP-соединения на стандартные порты вроде 443.

В этой ситуации UDP-транспорт VPN не «нестабилен» — он попросту не может установиться или обрывается сразу после хендшейка, и никакая тонкая настройка keepalive это не исправит, потому что пакеты режутся посередине пути. Здесь переключение на TCP (обычно на порт 443, чтобы трафик визуально не отличался от обычного HTTPS) — не костыль, а единственный рабочий вариант:

# OpenVPN клиент, конфиг для обхода блокировки UDP
proto tcp
port 443

SSTP по конструкции всегда работает через TCP 443 (это VPN-протокол Microsoft, обёрнутый в HTTP-подобный SSL-туннель) — именно поэтому он исторически популярен там, где UDP зарублен полностью, а любой TCP-трафик, кроме порта 443, тоже фильтруется.

Важная оговорка: даже в этом законном случае проблема TCP-in-TCP никуда не девается — при заметных потерях TCP-транспорт всё так же просядет сильнее, чем UDP при тех же потерях. Вы выбираете между «работает нестабильно» и «не работает вообще», и первое лучше второго. Если сеть одновременно блокирует UDP и сама по себе нестабильна, TCP спасёт от блокировки, но не от потерь — это разные проблемы, и решать их придётся раздельно.

Более современная альтернатива для сетей с агрессивной DPI-фильтрацией — протоколы, которые остаются на UDP, но маскируются под легитимный UDP-трафик (видеозвонки, QUIC/HTTP3), а не переходят на TCP в лоб. Разбор таких вариантов — в статье про Hysteria2 и TUIC для обхода DPI: они позволяют остаться в мире UDP там, где обычный OpenVPN или WireGuard блокируются по протоколу, но грубая блокировка «весь UDP кроме DNS» их всё равно не пропустит — тогда действительно остаётся только TCP.

Итоговое правило простое: переключение на TCP лечит блокировку, а не нестабильность. Если у вас настоящая блокировка UDP — переключайте, это оправданно. Если у вас просто плохой канал с потерями — TCP сделает только хуже, а решать нужно диагностикой реальной причины потерь, MTU и keepalive-интервалами на UDP-транспорте.

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

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

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

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

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

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

Если TCP «надёжный», почему вложенный TCP не спасает, а вредит при потерях?

Надёжность TCP работает на уровне одного соединения между двумя честными оценками RTT и потерь. Когда вы вкладываете TCP в TCP, оба слоя пытаются независимо восстановить один и тот же поток данных, каждый со своей, искажённой другим слоем оценкой сети — вместо одной согласованной реакции на потерю получаются две несогласованные, конфликтующие.

WireGuard вообще можно перевести на TCP?

Нет, в стандартном WireGuard транспорт — только UDP, это часть архитектуры протокола, а не настраиваемый параметр. Обёртки поверх WireGuard, которые заявляют поддержку TCP (некоторые прокси-обвязки), на деле добавляют отдельный TCP-туннель поверх UDP-пакетов WireGuard — и наследуют ровно ту же проблему TCP-in-TCP, если внутри идёт TCP-трафик приложений.

Как понять, что причина нестабильности — именно блокировка UDP, а не потери пакетов?

Проверьте, устанавливается ли UDP-соединение вообще: если хендшейк не проходит ни разу или обрывается сразу после установки на конкретной сети (но нормально работает на других сетях с тем же клиентом), это похоже на блокировку. Если соединение устанавливается и работает, но с рывками и просадками — это потери на линии, и здесь нужно смотреть mtr, а не менять транспорт.

UDP-транспорт VPN сам по себе теряет данные приложений из-за отсутствия гарантий доставки?

Нет — гарантии доставки для приложений, которым они нужны (веб, SSH, RDP), обеспечивает их собственный внутренний TCP-слой, а не транспорт VPN. UDP-транспорт VPN просто честно передаёт то, что дошло, и не мешает внутреннему TCP делать свою работу корректно.

Есть ли протоколы, где проблема TCP-in-TCP не возникает даже на TCP-транспорте?

Да, если сам инкапсулируемый трафик — не TCP, а, например, UDP-игровой трафик или DNS-запросы. Проблема специфична именно для сочетания «TCP внутри TCP»; при UDP-трафике внутри TCP-туннеля классического мельтдауна с двумя борющимися ретрансмиссиями не возникает, хотя задержка и накладные расходы от TCP-транспорта остаются.

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

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

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