От HAProxy до VLESS+Reality: все грабли одного MTProto-прокси

от автора

От HAProxy до VLESS+Reality: все грабли одного MTProto-прокси

Если вы когда-нибудь поднимали свой MTProto-прокси для Telegram и он у вас «работает, но как-то не очень» — эта статья для вас. Я прошёл путь от «просто добавить relay» до «переписать всю цепочку на VLESS+Reality с нуля», и по дороге собрал приличную коллекцию граблей. Расскажу всё по порядку, чтобы вам не пришлось наступать на те же.

Исходная ситуация

Была задача простая на первый взгляд: поднять MTProto проксю для Telegram на сервере в РФ. Причина стандартная — хочется, чтобы телега работала

Взял mtg (ghcr.io/9seconds/mtg:2 — отличная реализация MTProto-прокси с поддержкой fake-TLS), поднял на сервере — и оно не работает. Точнее, работает, но еле-еле: то подключается, то нет, на мобильном интернете почти всегда фейл.

Первая мысль, которая приходит в голову почти всем в такой ситуации: «провайдер блокирует адреса Telegram». И это отчасти правда — но, как выяснилось, правда не вся.

Первая попытка: спрятать Telegram за релеем

Логика была такая: раз провайдер режет соединения именно к IP Telegram, то уберу эти IP из виду. Арендую второй сервер за границей, туда ставлю настоящий mtg, а на RU-сервере — просто TCP-релей (HAProxy или голый iptables DNAT), который прозрачно перекидывает байты дальше.

Клиент → RU-сервер (HAProxy, чистый TCP passthrough) → Зарубежный сервер (mtg) → Telegram

Казалось бы, логично: сервер в РФ теперь физически ничего не знает про Telegram, он просто гоняет байты во внешний IP. Провайдер не должен видеть ничего подозрительного.

Не сработало. И вот тут первая важная вещь, которую стоит понять сразу, чтобы не терять на неё время, как я: HAProxy в режиме TCP (mode tcp) — это чистый passthrough на уровне байтов. Он не меняет ни один бит в трафике. А значит, если DPI-система провайдера умеет распознавать сигнатуру самого MTProto/fake-TLS протокола (а современные системы это умеют, обфускация MTProto далеко не идеальна против активного анализа), то ей совершенно не важно, куда в итоге идёт этот трафик — хоть в Telegram напрямую, хоть транзитом через десять серверов. Она видит характерный паттерн прямо на границе сети провайдера и давит/тормозит его там же.

Проверил гипотезу с портом — может, дело в нестандартном порту 9443? Переставил на 8080, на 443 — без разницы. Заменил HAProxy на голый DNAT через iptables (чтобы вообще убрать userspace-прокси из цепочки) — тот же результат. Значит, дело не в конкретной реализации relay и не в порту — дело в самом факте, что через сеть этого провайдера идёт трафик, похожий на MTProto.

Вторая попытка: SOCKS5 вместо голого relay

Ладно, чистый TCP passthrough не работает — попробуем завернуть трафик во что-то менее прозрачное. mtg умеет из коробки ходить через upstream SOCKS5-прокси (секция [network] proxies в конфиге) — это открывает дорогу к архитектуре «mtg работает локально на RU-сервере, а наружу к Telegram ходит через SOCKS5-туннель на зарубежный сервер»:

Поднял простенький SOCKS5-сервер на зарубежном хосте, всё настроил — и снова нестабильно, местами даже хуже, чем relay. Проверил разные реализации SOCKS5-сервера (сначала лёгкий Go-шный прокси, потом Xray в режиме socks-inbound) — разницы никакой. Значит, дело не в конкретной реализации SOCKS5.

И тут стало ясно: SOCKS5 — это ТОЖЕ обычный незашифрованный TCP-туннель, без какой-либо маскировки. Если провайдер детектит характерный паттерн MTProto-трафика на границе своей сети, ему всё равно, обёрнут этот трафик в HAProxy-relay или в SOCKS5-CONNECT — сигнатура protocol’а внутри никуда не делась.

Третья попытка: настоящий VLESS+Reality

Вот тут наконец нашёлся правильный инструмент. VLESS+Reality — это протокол, специально спроектированный для того, чтобы противостоять именно активному DPI-анализу. В отличие от fake-TLS в MTProto (который просто ИМИТИРУЕТ структуру TLS-хендшейка), Reality делает настоящий TLS 1.3 хендшейк с настоящим сайтом — сервер буквально ходит на реальный домен (например, www.samsung.com или любой другой популярный сайт с хорошей TLS 1.3 + X25519 поддержкой) и использует часть его сертификата в своём ответе. Для DPI-системы, которая пассивно смотрит на трафик, это неотличимо от обычного похода пользователя на этот сайт.

Новая схема:

Клиент → mtg (RU-сервер, локально)

→ SOCKS5 (localhost)

→ Xray-клиент (VLESS+Reality исходящий)

→ Xray-сервер (зарубежный, VLESS+Reality входящий)

→ freedom outbound → Telegram

Собрал всё на голом Xray (без готовых панелей вроде 3x-ui — если хотите держать инфраструктуру минимальной и понятной, голого xray-core в docker-контейнере более чем достаточно). Подключился — и… соединение мгновенно рвётся. Ноль байт в ответ, обрыв через доли секунды.

Грабля №1: xtls-rprx-vision

Первая попытка конфига VLESS-outbound включала модный flow: "xtls-rprx-vision" — это оптимизация, которая распознаёт внутреннюю структуру TLS-записей проксируемого трафика и переключается на «прямую склейку» сокетов для меньших накладных расходов (оф док). Звучит отлично, если вы проксируете чужой TLS-трафик (например, обычный веб-сёрфинг через VLESS).

Но мы туннелируем не TLS, а сырые байты протокола MTProto — они просто ЗАКОДИРОВАНЫ под fake-TLS на этапе клиент↔наш сервер, а вот на этапе сервер↔сервер это уже произвольный бинарный поток без какой-либо TLS-структуры внутри. Vision пытается распарсить эти байты как TLS-записи, не находит ожидаемой структуры — и, судя по всему, обрывает соединение как «что-то пошло не так».

Решение: не используйте flow: "xtls-rprx-vision", если туннелируете не-TLS payload. Просто уберите поле flow из конфига клиента и сервера — обычный VLESS+Reality без Vision прекрасно передаёт произвольные байты.

Грабля №2: «протухший» IP — а может, и нет

Убрал Vision — соединение стало устанавливаться, но проходило только процентов 40-60 попыток. Естественная мысль: зарубежный ip уже светился и Telegram придерживает соединения именно с этого IP из-за накопленной «плохой репутации»?

Проверил напрямую: взял свежий сервер у другого провайдера, без единого байта истории. Задеплоил тот же Reality-сервер. Результат — те же самые 40-60% успешных подключений. Гипотеза не подтвердилась: дело было не в конкретном IP.

Грабля №3: цена TLS-хендшейка на каждое подключение

А дело оказалось вот в чём. Telegram-клиент открывает много коротких параллельных TCP-соединений — это нормальное поведение MTProto (разные соединения под разные типы запросов: обновления, медиа, служебные пакеты). У каждого такого соединения довольно короткое «терпение» — если оно не получает первый байт ответа за секунду-другую, клиент его обрывает и пробует заново.

А теперь смотрим, что происходит на нашей стороне: каждое новое MTProto-подключение клиента = новое VLESS-соединение = новый полный Reality TLS-хендшейк (это минимум пара round-trip’ов до зарубежного сервера и обратно, плюс сама Reality-механика). Секунда терпения клиента улетает на установку тоннеля, а полезные данные ещё даже не начали идти.

Собрал точную временную линию по логам и tcpdump: клиент стартует стрим, наш Xray-клиент принимает SOCKS5-запрос и начинает VLESS-хендшейк — и где-то через 700-900 мс клиент уже обрывается, не дождавшись ни байта.

Решение — мультиплексирование (mux). Xray умеет прогонять много логических потоков через один уже установленный VLESS-туннель, не переустанавливая TLS-хендшейк каждый раз:

Добавляется в конфиг outbound на клиентской стороне (там, где VLESS исходящий). После этого изменения тоннель наконец начал держать нагрузку: логи чистые, ни одной ошибки установки соединения.

Финальная неожиданность: дело было не только в архитектуре

После всех этих фиксов картина улучшилась, но всё ещё была нестабильной именно на исходном RU-сервере. На этом моменте я взял тестовый сервер у другого облачного провайдера в России и развернул на нём точно ту же связку (mtg + mux + VLESS/Reality) — и она заработала идеально сразу, без единой ошибки в логах.

Вывод оказался неожиданно простым: проблема с самого начала была в сетевой политике конкретного хостинг-провайдера первого RU-сервера, а не в архитектуре как таковой. Он, судя по всему, применяет DPI на границе своей сети и режет/тормозит распознанные MTProto-паттерны — причём делает это независимо от того, во что вы этот трафик заворачиваете, если только это не выглядит как настоящий TLS с реального сайта.

## Итоговая архитектура

Два сервера, три контейнера. RU-сервер держит mtg и Xray-клиент в одном docker-compose стеке (они общаются между собой по внутренней docker-сети, наружу торчит только порт mtg). Зарубежный сервер держит только Xray с VLESS+Reality входом.

Как повторить у себя

Шаг 1. Зарубежный сервер — точка выхода в Telegram

Нужен сервер с адекватной (не заблокированной вашим локальным DPI) связью до Telegram. Подойдёт практически любой недорогой VPS за пределами вашей юрисдикции.

Генерируем ключи Reality и UUID клиента:

docker run --rm teddysun/xray xray x25519
# выведет PrivateKey и Password (это PublicKey)
openssl rand -hex 8 # short ID
cat /proc/sys/kernel/random/uuid # UUID клиента

Конфиг /opt/xray-reality/config.json:

{ "log": {"loglevel": "warning"}, "inbounds": [{ "listen": "0.0.0.0", "port": 8443, "protocol": "vless", "settings": { "clients": [{"id": "ВАШ_UUID"}], "decryption": "none" }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "show": false, "dest": "ВАШ_ДЕСТ_ДОМЕН:443", "xver": 0, "serverNames": ["ВАШ_ДЕСТ_ДОМЕН"], "privateKey": "ВАШ_PRIVATE_KEY", "shortIds": ["ВАШ_SHORT_ID"] } } }], "outbounds": [{"protocol": "freedom", "tag": "direct"}]}

Про выбор dest-домена: нужен реальный, живой сайт с поддержкой TLS 1.3 и X25519, желательно не самый заезженный пример из туториалов (Xray сам предупредит в логах, если выбор слишком очевидный — тогда лучше взять что-то менее популярное в качестве примера Reality-камуфляжа, но всё ещё крупное и легитимное).

docker-compose.yml:

services: xray-reality: image: teddysun/xray:latest container_name: xray-reality volumes: - "./config.json:/etc/xray/config.json:ro" ports: - "8443:8443" restart: unless-stopped

cd /opt/xray-reality && docker compose up -d

Шаг 2. RU-сервер (или любой другой сервер, который будет точкой входа для клиентов)

Генерируем mtg secret с fake-TLS маскировкой под нужный домен:

docker run --rm ghcr.io/9seconds/mtg:2 generate-secret -x ВАШ_ДОМЕН_МАСКИРОВКИ

Конфиг /opt/mtg-vless/xray-client.json:

{ "log": {"loglevel": "warning"}, "inbounds": [{ "listen": "0.0.0.0", "port": 1080, "protocol": "socks", "settings": {"auth": "noauth", "udp": false} }], "outbounds": [{ "protocol": "vless", "settings": { "vnext": [{ "address": "IP_ЗАРУБЕЖНОГО_СЕРВЕРА", "port": 8443, "users": [{"id": "ВАШ_UUID", "encryption": "none"}] }] }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "show": false, "fingerprint": "chrome", "serverName": "ВАШ_ДЕСТ_ДОМЕН", "publicKey": "ВАШ_PUBLIC_KEY", "shortId": "ВАШ_SHORT_ID" } }, "mux": { "enabled": true, "concurrency": 32 }, "tag": "vless-out" }]}

Конфиг /opt/mtg-vless/mtg.toml:

secret = "ВАШ_MTG_SECRET"
bind-to = "0.0.0.0:443"
public-ipv4 = "IP_ЭТОГО_СЕРВЕРА"
prefer-ip = "only-ipv4"

[network]
proxies = [
"socks5://xray-client:1080"
]

docker-compose.yml — обратите внимание, оба сервиса в одном файле, чтобы mtg мог обращаться к xray-client по имени через встроенный docker DNS:

services: xray-client: image: teddysun/xray:latest container_name: xray-client volumes: - "./xray-client.json:/etc/xray/config.json:ro" restart: unless-stopped mtg: image: ghcr.io/9seconds/mtg:2 container_name: mtg-vless command: ["run", "/mtg.toml"] volumes: - "./mtg.toml:/mtg.toml:ro" ports: - "443:443" depends_on: - xray-client restart: unless-stopped

cd /opt/mtg-vless && docker compose up -d

Шаг 3. Проверка перед реальным тестом

mtg умеет сам себя тестировать — прогоняет пробные подключения ко всем дата-центрам Telegram через ваш прокси-конфиг:

docker run --rm --network mtg-vless_default -v /opt/mtg-vless/mtg.toml:/mtg.toml:ro \
ghcr.io/9seconds/mtg:2 doctor /mtg.toml

Смотрите на блок Validate network connectivity with proxy socks5://xray-client:1080 — там должны быть зелёные галочки по всем DC. Блок про «native network connectivity» (прямое соединение мимо тоннеля) закономерно будет красным — это нормально, мы его не используем.

Шаг 4. Ссылка для клиентов

tg://proxy?server=IP_ВАШЕГО_ВХОДНОГО_СЕРВЕРА&port=443&secret=ВАШ_MTG_SECRET

Чек-лист граблей, чтобы не наступать повторно

Не используйте flow: "xtls-rprx-vision", если туннелируете не настоящий TLS-трафик, а что-то другое (в нашем случае — MTProto). Vision ломает произвольные бинарные потоки.

Обязательно включайте mux на клиентском VLESS outbound. Без него каждое новое короткое подключение платит полную цену TLS-хендшейка, и быстро ретраящие клиенты (а MTProto именно такой) никогда не дождутся ответа.

prefer-ip = "only-ipv4" в конфиге mtg — если у вашего сервера IPv6 настроен криво или не тестировался, дефолтное предпочтение IPv6 добавит случайные подвисания.

Простой TCP-relay (HAProxy, iptables DNAT, голый SOCKS5) не прячет протокол от DPI. Если ваш провайдер умеет распознавать сигнатуру MTProto/fake-TLS, ему всё равно, во что вы это заворачиваете, пока это не выглядит как настоящий TLS до реального сайта.

Проверяйте гипотезу «провайдер блокирует» на РАЗНЫХ провайдерах, прежде чем чинить архитектуру до бесконечности. Иногда дешевле и быстрее взять тестовый сервер у другого хостера и сравнить, чем неделями оптимизировать код вокруг проблемы, которая находится вне вашего контроля.

Заключение

MTProto-прокси в 2026 году в РФ — это уже не просто «поднял бинарник и работает». Провайдерский DPI умеет распознавать протокол, и обычная маскировка через relay или SOCKS5 больше не спасает. VLESS+Reality на сегодня — один из немногих реально рабочих способов сделать трафик неотличимым от обычного HTTPS для пассивного анализа. Но даже с правильным протоколом дьявол в деталях: flow, mux, prefer-ip — каждая мелочь может превратить «почти работает» в «не работает вообще».

Если у вас похожая ситуация — начните с самого дешёвого теста: проверьте прямую связность с зарубежным сервером до Telegram, прежде чем городить сложную архитектуру. Может оказаться, что дело не в вашем коде, а в конкретном хостере — и тогда вся экономия времени будет в том, чтобы просто сменить сервер.

Удачи!

ссылка на оригинал статьи https://habr.com/ru/articles/1075528/