Каждая публикуемая заметка получила callout-шапку со ссылкой на свою страницу wiki.zapret.moe (на самой вики она вырезается транформером RemoveMirrorCallout, видна только на зеркале Obsidian Publish и в Forgejo) и SEO-поле description — 1–2 предложения для meta description обоих сайтов. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
18 KiB
| date | tags | aliases | link | description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-06-07 |
|
|
https://github.com/sleep3r/mtproto.zig | Пошаговая настройка MTProxy на mtproto.zig: установка mtbuddy, config.toml, TCPMSS-дробление, nfqws-desync, диагностика блокировок ТСПУ по метрикам. |
[!mirror] Резервное зеркало Актуальная версия этой страницы — на основной вики: wiki.zapret.moe/mtproxy/mtproto-zig-setup
🛠️ Настройка MTProxy на mtproto.zig — пошагово и «что происходит на проводе»
[!info] Что это и для кого Практический runbook: как поднять MTProxy на mtproxy/mtproto-zig и понимать, что делает каждый слой защиты — с учётом свежих наблюдений (протухший фингерпринт клиента из tdesktop#30733, детект по
expected_64_got_0, приёмы TCPMSS и SYN-ACK).Теория и «почему» — в обзорной статье mtproxy/mtproto-zig. Здесь — команды, конфиг и диагностика.
[!tip] Почему mtproto.zig, а не telemt Оба — хорошие FakeTLS-прокси.
mtproto.zigберут, когда нужен обход DPI «под ключ»: он сам ставит TCPMSS-дробление + nfqws-desync + nginx-маскировку одной командой, без ручного iptables. telemt — когда нужен REST API для бота (Zapret/mtproto/02-implementations).
Шаг 0. Подготовка
| Что | Как и почему |
|---|---|
| VPS / подсеть | Не «народный» хостинг (Selectel/Я.Облако — Сигнал 1 [[VLESS/dpi-tls-june-2026 |
| Домен маскировки | Популярный, с одним раундом x25519: rutube.ru, ozon.ru, vk.com, yandex.ru. НЕ wb.ru и др. HRR/secp521r1. |
| Порт | 443. Любой другой подозрителен. |
| Доступ | root/sudo на сервере. |
⚠️ Домен вшивается в ссылку
tg://и неизменен после раздачи. Выбирай один раз.
Шаг 1. Установка
# 1. bootstrap mtbuddy (проверяет minisign-подпись + SHA-256)
curl -fsSL https://raw.githubusercontent.com/sleep3r/mtproto.zig/v1.10.3/deploy/bootstrap.sh | sudo bash
# 2. установка прокси со всеми DPI-модулями
sudo mtbuddy install --port 443 --domain rutube.ru --yes
Что делает install (--no-dpi отключает п.5–6):
- Качает готовый бинарь (определяет CPU:
x86_64_v3→x86_64→aarch64) - Генерит секрет (или
--secret <32hex>) - systemd-сервис
mtproto-proxy - Открывает порт в
ufw - TCPMSS=88 iptables (дробит ClientHello) ← см. Шаг 4
- nginx-маскировка + nfqws-desync ← см. Шаг 5
- Печатает
tg://-ссылку
Полезные флаги: --secret, --user, --tcpmss <n> (дефолт 88), --no-tcpmss, --no-nfqws, --no-masking, --ipv6-hop.
Шаг 2. Конфиг config.toml
Большинство — уже дефолты; фиксируем ключевое явно (/opt/mtproto-proxy/config.toml):
[general]
use_middle_proxy = true # медиа на не-Premium + promo-теги
[server]
port = 443
# rate_limit_per_subnet = 0 # ОСТАВЬ 0 для мобильных юзеров РФ (carrier-NAT)
[censorship]
tls_domain = "rutube.ru" # single-round x25519, неизменен после раздачи
mask = true # форвард зондов на реальный домен (анти-probing)
fake_tls_only = true # реджектить палевный dd-транспорт
# desync = true # дефолт on — дробит ServerHello (1 байт + 3мс)
drs = true # мимикрия размеров TLS-записей под браузер
fast_mode = true
[metrics]
enabled = true # Prometheus /metrics (см. Шаг 6 — диагностика)
host = "127.0.0.1"
port = 9400
[access.users]
user1 = "00112233445566778899aabbccddeeff" # openssl rand -hex 16
После правки: sudo systemctl restart mtproto-proxy (SIGHUP-reload тоже есть, но при workers>1 запрещён).
Шаг 3. Что происходит на проводе (карта защит)
КЛИЕНТ (Telegram) ТСПУ СЕРВЕР (mtproto.zig)
│ ── ClientHello (почерк!) ──────► 👁 фингерпринт ──────► снимает fp в лог
│ │ (тут и блок #30733)
│ ◄──────────── ServerHello (дроблён desync) ──────────── mask/desync/drs
│ ── 64-байт MTProto-хендшейк ───► (если жив) ───────────► proxy → DC
| Слой | Что делает | Против чего |
|---|---|---|
| TCPMSS=88 | клиент режет ClientHello на ~6 кусков | фингерпринт (DPI не пересобирает) |
| nfqws desync | fake-пакеты + TTL-split (S→C) | stateful DPI |
| desync ServerHello | 1 байт + 3мс + хвост | пассивные сигнатуры |
| mask | зонды → реальный tls_domain |
active probing |
| drs | размеры записей как у браузера | статистика трафика |
[!danger] Слабое звено, которое сервер НЕ чинит — фингерпринт клиента Почерк ClientHello (JA3/JA4) генерирует приложение Telegram, не сервер. По tdesktop#30733: Desktop мимикрирует под Chrome 134/macOS (
t13d1516h2_8daaf6152771_d8a2da3f94cd), а живой Chrome — 148 → пресет протух, и это маркер. Это тот самый блок, дающийexpected_64_got_0(Zapret/mtproto/10-telemt-logs-dpi).Лечится только в самом Telegram. Всё, что может сервер — спрятать/сломать опознание этого почерка (TCPMSS, desync), а не поправить его. Поэтому Шаги 4–6 важны.
Шаг 4. TCPMSS — дробление ClientHello
Как работает
Сервер анонсирует в SYN-ACK малый MSS, и клиент вынужден резать всё исходящее (включая ClientHello ~517 байт) на мелкие сегменты. DPI без потоковой пересборки не складывает почерк целиком → не матчит сигнатуру.
В mtproto.zig включено по умолчанию (TCPMSS=88). Сменить значение: mtbuddy install --tcpmss 96.
Вариант «через балансировщик» — помогает?
Да, помогает. Это тот же механизм. Если перед прокси стоит балансировщик (он терминирует клиентский TCP), MSS-clamp надо вешать именно на балансировщик — там рождается SYN-ACK к клиенту:
iptables -t mangle -A OUTPUT -p tcp --sport 443 \ --tcp-flags SYN,ACK SYN,ACK -j TCPMSS --set-mss 96Конфиг telemt/mtproto.zig при этом трогать не надо — правило работает на уровне ядра.
Почему «на балансировщике»: clamp на бэкенде (где сидит сам прокси) не дойдёт до клиента, если балансировщик пере-устанавливает TCP. Правило должно быть на той коробке, что шлёт SYN-ACK клиенту.
[!note] 88 или 96 — разница невелика Оба дают ~6 сегментов на ClientHello. mtproto.zig по умолчанию 88; совет из интернета — 96. Бери любое; если ставишь mtproto.zig напрямую (без отдельного балансировщика) — TCPMSS уже стоит, дублировать руками не нужно.
[!tip] Это и есть «решение JA4» из teleproxy Когда говорят «в teleproxy решён JA4» — речь именно об этой фрагментации: teleproxy ставит
TCP_MAXSEG=256, чтобы DPI не извлёк JA4 из первого пакета. JA4 при этом не меняется (его задаёт клиент). mtproto.zig делает то же самое и агрессивнее (MSS=88), так что этот приём у тебя уже включён. Почему это не «смена почерка» и где лежит настоящий фикс — mtproxy/ja4-sni-client-side.
[!warning] Дробление ≠ панацея MSS-clamp бьёт по DPI, который не пересобирает поток. Если ТСПУ делает реассемблинг — одного дробления мало, нужен desync (nfqws, Шаг 5), который активно ломает пересборку fake-пакетами. Поэтому их ставят вместе.
Шаг 5. nfqws TCP desync + тюнинг TTL
Ставится при install. Стратегия: --dpi-desync=fake,split2 --dpi-desync-ttl=6 --dpi-desync-fooling=md5sig — fake-пакет с заниженным TTL (долетает до ТСПУ, умирает до клиента) + битая MD5-опция, чтобы сбить state-машину DPI.
TTL надо подтюнить под маршрут (дефолт 6 не универсален, «4–8 для росс. ISP»):
traceroute <ip_клиента_или_DC> # прикинуть хоп, где сидит ТСПУ
sudo mtbuddy nfqws --ttl 7 # переставить
systemctl status nfqws-mtproto # проверить, что запущен
TTL должен быть больше расстояния до ТСПУ, но меньше расстояния до клиента.
Шаг 5.5 (опционально). Egress через Xray/SOCKS5 или туннель
Это аналог SOCKS5_PROXY + DIRECT_MODE из конфигов teleproxy: маршрут
исходящего трафика прокси к дата-центрам Telegram через Xray/VLESS (SOCKS5)
или WireGuard/AmneziaWG-туннель.
[upstream]
type = "socks5" # "direct" | "tunnel" | "socks5" | "http"
[upstream.socks5]
host = "127.0.0.1"
port = 1080 # порт локального Xray/VLESS
# username = ""
# password = ""
Для туннеля вместо SOCKS5:
[upstream]
type = "tunnel"
[upstream.tunnel]
interfaces = ["awg0", "awg1"] # WireGuard/AmneziaWG, с авто-фолбэком
[!important] Что это лечит, а что нет Egress-маршрут помогает, когда дата-центры Telegram недоступны с твоего VPS (заблокированы/режутся на пути proxy→DC), или нужен лишний хоп. Это путь сервер→DC — на входящий ClientHello (где JA4, который видит ТСПУ у клиента) он не влияет. Не путай: это про доступность DC, а не про обход детекта почерка. См. mtproxy/ja4-sni-client-side.
Шаг 6 (опционально). Лимит SYN-ACK — десинхрон + анти-залп
На пальцах: сервер иногда «роняет» свой ответ при установке соединения (SYN-ACK), клиент переспрашивает через секунду. От этого DPI сбивается со счёта и не опознаёт почерк клиента, а соединения идут не пачкой, а по одному в секунду. Спорный, но у людей рабочий приём.
Подробная механика (с аналогией), цена и per-port вариант (бюджет 1/сек на каждый порт, чтобы не калечить всех юзеров) — в Zapret/mtproto/10-telemt-logs-dpi#Лимит SYN-ACK — помогает или нет (для mtproto.zig всё идентично, только порт 443).
Коротко: ставить стоит, если блок держится после Шагов 4–5; брать сразу per-port; мониторить логи (Шаг 7), чтобы поймать адаптацию ТСПУ.
Шаг 7. Диагностика — mtproto.zig сам показывает атаку DPI
Фингерпринт клиента в логах
mtproto.zig логирует почерк первых 16 ClientHello (диагностический бюджет):
journalctl -u mtproto-proxy | grep "client ClientHello"
# client ClientHello [ciphers=... groups=... key_share=...] (we serve: ...)
[!tip] Как связать с #30733 Смотри
key_shareв логе. Свежий браузер шлётX25519MLKEM768(post-quantum). Если твой клиент его не шлёт — он на старом пресете и попадает под детект из #30733. Это прямой способ увидеть «протух ли почерк» на своём трафике.
Метрики close-reason — детектор начала блокировок
mtproto.zig отдаёт Prometheus-метрику с причинами закрытия — её всплеск = ТСПУ начал резать:
curl -s 127.0.0.1:9400/metrics | grep -E "close_reason|handshake_timeouts"
# mtproto_connection_close_reason_total{reason="tls_validation_failed"} ...
# mtproto_connection_close_reason_total{reason="replay_detected"} ... ← зонды Revisor
# mtproto_connection_close_reason_total{reason="bad_handshake"} ...
# mtproto_handshake_timeouts_total ... ← аналог expected_64_got_0
Рост tls_validation_failed / replay_detected / handshake_timeouts над фоном — это и есть сигнал, что цензор начал работать по тебе (так и задумано разработчиком). replay_detected отдельно ловит active-probe зонды ТСПУ (Revisor).
Плюс есть веб-дашборд (порт 61208, Basic-auth, токен в /opt/mtproto-proxy/monitor/dashboard.token) — открывать только через SSH-тоннель.
Когда всё-таки заблокировали — порядок действий
- Проверь логи/метрики (Шаг 7): растёт ли
handshake_timeouts/tls_validation_failed, и какойkey_shareу падающих клиентов. - Подтюнь TTL nfqws (Шаг 5) — частая причина, что desync «не достаёт» до ТСПУ.
- Снизь MSS (
--tcpmss 80) или добавь clamp на балансировщик (Шаг 4). - Включи SYN-ACK per-port (Шаг 6).
- Если рвётся путь до DC (а не вход) — egress через Xray/SOCKS5 или туннель (Шаг 5.5).
- Смени узел/подсеть (Сигнал 1) — если IP/диапазон попал под раздачу.
- Не дёргай настройки рефлекторно под блоком — сам паттерн адаптации может усугубить.
- Запасной канал — VLESS/dpi-tls-june-2026 на отдельном узле.
✅ Чек-лист
- VPS на «чистой» подсети, домен single-round x25519, порт 443
mask = true,fake_tls_only = true,drs = trueTCPMSSактивен (iptables -t mangle -S OUTPUT | grep TCPMSS) — или clamp на балансировщике- nfqws запущен, TTL подтюнен (
systemctl status nfqws-mtproto) [metrics] enabled = true+ мониторингclose_reason/handshake_timeouts- Проверил
key_shareклиента в логах (свежесть почерка, #30733) rate_limit_per_subnet = 0для мобильных юзеров РФ- Готов запасной VLESS/XHTTP
📚 См. также
- mtproxy/ja4-sni-client-side — почему смена почерка/SNI возможна только на клиенте, что из мер ниже реально серверное
- mtproxy/mtproto-zig — теория: как работает и почему
- Zapret/mtproto/10-telemt-logs-dpi — детект DPI, per-port nft
- 🔗 tdesktop#30733 — протухший фингерпринт
- Zapret/mtproto/05-censorship
- Zapret/mtproto/02-implementations
- VLESS/dpi-tls-june-2026
- VPS/VPS