mtproto.zig
September 8, 2026 · View on GitHub
mtproto.zig
Держите близких на связи.
Крошечный Telegram-прокси, который вы запускаете на своём сервере. Он прячется в обычном HTTPS — цензуре его не найти, а вашим близким не потерять. Установка одной командой, одна ссылка, чтобы поделиться.
177 КБ · меньше 1 МБ RAM · 0 зависимостей — да, он настолько лёгкий (подробности ниже ↓)
Технически: крошечный MTProto-прокси на Zig без зависимостей, маскирует трафик Telegram под обычный TLS 1.3 HTTPS.
| 🇬🇧 English | 🇷🇺 Русский | 🇨🇳 中文 | 🇮🇷 فارسی | 🇻🇳 Tiếng Việt |
|---|
Почему этот? · Установка · Обновление · Команды · Маршрутизация · Конфиг · Дашборд · Сборка · Docker · Безопасность · Совместимость · FAQ
Для кого это
- Вы там, где Telegram режут или блокируют, и хотите просто вернуть его.
- Вы — тот, к кому семья идёт за помощью, и хотите защитить родителей и друзей ссылкой, которую они нажимают один раз и больше о ней не думают.
Прокси работает на вашем сервере — ваши сообщения никогда не идут через наш, и регистрироваться нигде не нужно. Открытый код под лицензией MIT; прокси намеренно не пишет в логи ни секреты, ни кто подключается.
Чем это лучше VPN?
VPN заметен — цензор узнаёт протокол и блокирует его, а VPN на всё устройство медленный и сажает батарею. Этот прокси выглядит как обычный HTTPS-сайт, ведёт только Telegram, и тем, кому вы дали ссылку, не нужно ничего устанавливать: они нажимают одну ссылку, остальное Telegram делает сам. Помещается на самый дешёвый VPS, стартует мгновенно, больше ничего настраивать не нужно.
В сравнении с другими MTProto-прокси
Большинство MTProto-прокси крупные, тянут зависимости и потребляют много памяти. Этот проект устроен иначе:
| Proxy | Язык | Бинарник | Baseline RSS | Старт | Зависимости |
|---|---|---|---|---|---|
| mtproto.zig | Zig | 177 КБ | 0.75 МБ | < 10 мс | 0 |
| Official MTProxy | C | 524 КБ | 8.0 МБ | < 10 мс | openssl, zlib |
| Telemt | Rust | 15 МБ | 12.1 МБ | ~ 5-6 с | 423 crates |
| mtg | Go | 13 МБ | 11.6 МБ | ~ 30 мс | 78 modules |
| MTProtoProxy | Python | N/A | ~ 30 МБ | ~ 300 мс | python3, cryptography |
| JSMTProxy | Node.js | N/A | ~ 45 МБ | ~ 400 мс | nodejs, openssl |
Почему Zig?
Zig даёт производительность и минимальный footprint уровня C, но без привычной боли C-проектов:
- Без произвольных аллокаций: слоты соединений и буферы заранее выделяются на старте. Нет GC, который может уронить кадры под нагрузкой.
- Герметичная кросс-компиляция: можно запустить
zig buildна macOS и получить статически слинкованный Linux-бинарник. Без Docker и несовпаденийglibc. - Comptime: маппинг протокола, endian-конверсии и двуязычные строки
mtbuddyвычисляются при компиляции.
Прокси также включает набор техник обхода DPI:
| Техника | Что делает |
|---|---|
| Fake TLS 1.3 | Соединения выглядят как обычный HTTPS |
| DRS | Имитирует размеры TLS-record у Chrome/Firefox |
| Маскировка от активных проб | Если цензор проверяет ваш сервер, он получает настоящий TLS-хендшейк от локального веб-бэкенда (реальный серт, если домен ваш, иначе self-signed), а не молчащий прокси. Опционально: фронтить реальный tls_domain:443 для доменов с одноходовым x25519 |
| TCPMSS=88 | Дробит ClientHello на маленькие TCP-пакеты |
| nfqws TCP desync | Fake packets + TTL-limited splits против stateful DPI |
| Split-TLS | 1-байтовые Application records против пассивных сигнатур |
| VPN tunnel pool | Маршрутизация через WireGuard/AmneziaWG с SO_MARK и failover |
| IPv6 hopping | Авто-ротация IPv6 из /64 при банах через Cloudflare API |
| Anti-replay | Отбрасывает replay handshakes и активные пробы ТСПУ Revisor |
| Multi-user | Отдельные secret для разных пользователей |
| MiddleProxy | ME transport с автообновлением Telegram metadata |
MiddleProxy нужен для промо-тега и медиа на аккаунтах без Premium. Без него фото, видео, истории и другое медиа на non-Premium аккаунтах нужно считать недоступными, а не «иногда лагающими». Звонки Telegram не поддерживаются: Telegram ведёт звонки через SOCKS-пути, а такой трафик нельзя нормально замаскировать в mtproto.zig под обычный HTTPS.
Установка
Установка, обновление и управление делаются через mtbuddy — нативный Zig CLI, который поставляется вместе с прокси.
Одна команда
curl -fsSL https://raw.githubusercontent.com/sleep3r/mtproto.zig/main/deploy/bootstrap.sh | sudo bash
# Явно разрешить unsigned bootstrap mode (не рекомендуется)
curl -fsSL https://raw.githubusercontent.com/sleep3r/mtproto.zig/main/deploy/bootstrap.sh | sudo bash -s -- --insecure
# или: MTPROTO_INSECURE=1
Скрипт скачивает последний mtbuddy, проверяет minisign-подпись и SHA-256 checksum из GitHub Release, затем запускает mtbuddy --help. После этого установите прокси:
# Минимально: secret генерируется автоматически, DPI-модули включены
sudo mtbuddy install --port 443 --domain rutube.ru --yes
# Свой secret и имя пользователя
sudo mtbuddy install --port 443 --domain rutube.ru --secret <32-hex> --user alice --yes
# Без DPI-модулей, только bare proxy
sudo mtbuddy install --port 443 --domain rutube.ru --no-dpi --yes
# Установка из существующего config.toml
sudo mtbuddy install --config /path/to/config.toml --yes
# Явно разрешить unsigned mode (не рекомендуется)
sudo mtbuddy install --insecure --port 443 --domain rutube.ru --yes
В конце mtbuddy напечатает готовую tg:// ссылку для подключения.
Интерактивный мастер
sudo mtbuddy --interactive
Демо: интерактивная установка
Что делает установка
- Скачивает готовый бинарник прокси из GitHub Releases.
- Генерирует случайный secret или использует
--secret. - Создаёт systemd service
mtproto-proxy. - Открывает порт в
ufw, если он активен. - Применяет iptables-правила TCPMSS=88.
- Настраивает Nginx masking и nfqws TCP desync, если не указан
--no-dpi. - Печатает
tg://ссылку.
Параметры установки
| Флаг | По умолчанию | Описание |
|---|---|---|
--port, -p | 443 | Порт прокси |
--public-port | — | Порт, который будет указан в Telegram-ссылках |
--domain, -d | rutube.ru | Домен TLS-маскировки (⚠️ неизменен — см. примечание ниже) |
--secret, -s | auto | User secret, 32 hex chars |
--user, -u | user | Имя пользователя в config.toml |
--config, -c | — | Использовать существующий config.toml |
--yes, -y | — | Пропустить подтверждение |
--max-connections <N> | 512 | Максимум соединений |
--bind, -b | — | Bind на конкретный IP |
--no-masking | — | Выключить Nginx masking |
--no-nfqws | — | Выключить nfqws TCP desync |
--no-tcpmss | — | Выключить TCPMSS=88 |
--no-dpi | — | Выключить все DPI-модули |
--middle-proxy | — | Включить Telegram MiddleProxy relay |
--ipv6-hop | — | Включить IPv6 auto-hopping |
--version, -v <tag> | latest | Версия релиза |
--insecure | — | Разрешить unsigned assets (не рекомендуется) |
⚠️ Выберите
--domainодин раз. tg://-ссылки вшиваютtls_domain, поэтому смена домена на живом сервере (в т.ч. черезmtbuddy setup masking --domain …) инвалидирует все уже розданные ссылки. См. ARCHITECTURE.md / COMPATIBILITY.md.
Обновление
# Обновиться до последнего релиза
sudo mtbuddy update
# Зафиксировать конкретную версию
sudo mtbuddy update --version v0.11.1
# Явно разрешить unsigned mode (не рекомендуется)
sudo mtbuddy update --insecure
Команды mtbuddy
config validate, doctor и print-effective находят типичные ошибки TOML:
строки без кавычек, повторные секции/ключи и незакрытые кавычки. Диагностика указывает
строку и способ исправления, но не изменяет файл. Например, добавляйте
public_ip = "proxy.example.com" в существующую секцию [server], не создавая
вторую. Эта проверка дополняет проверку значений в прокси; полной реализацией TOML
она не является.
При ошибке чтения конфига дашборд показывает её расположение и продолжает обновлять показатели системы и процесса. Настройки и управление недоступны до исправления файла; следующее обновление страницы подхватит исправленный конфиг.
# Статус прокси и модулей
sudo mtbuddy status
# Проверка и просмотр конфига
sudo mtbuddy config validate
sudo mtbuddy config doctor
sudo mtbuddy config doctor --network
sudo mtbuddy config print-effective
# Напечатать ссылки из config.toml (по умолчанию FakeTLS ee; +dd при fake_tls_only=false; секретный вывод)
sudo mtbuddy links
sudo mtbuddy links --server proxy.example.com --config /opt/mtproto-proxy/config.toml
# Сгенерировать новый 32-hex secret
mtbuddy secret
# Hot-reload config
sudo mtbuddy reload
# Настроить DPI-модули после установки
sudo mtbuddy setup masking --domain rutube.ru
sudo mtbuddy setup nfqws
sudo mtbuddy setup recovery
# Опциональное ограничение SYN на уровне ядра (анти-флуд, по умолчанию ВЫКЛ).
# Дропает абьюзные SYN-всплески в ядре ДО accept(); отдельный systemd-oneshot,
# поэтому CAP_NET_ADMIN не выдаётся прокси. Пресет по умолчанию безопаснее для CGNAT.
sudo mtbuddy setup syn-limit --preset soft # soft 2/с·5 | medium 1/с·3 | hard 1/с·1
sudo mtbuddy setup syn-limit --status
sudo mtbuddy setup syn-limit --remove
# WEB-прокси для Telegram Desktop 7.1+ (MTProto внутри обычного HTTPS-сайта).
# Нужен ваш домен с A-записью на этот хост.
sudo mtbuddy setup web --domain relay.example.com
sudo mtbuddy setup web --domain relay.example.com --mode behind # TLS терминируете вы (CDN, отдельный IP)
sudo mtbuddy setup web --remove
# Установить web dashboard
sudo mtbuddy setup dashboard
# Удалить только дашборд (прокси продолжит работать)
sudo mtbuddy setup dashboard --remove
# VPN tunnel pool
sudo mtbuddy setup tunnel /path/to/awg0.conf
sudo mtbuddy setup tunnel 'vpn://...'
sudo mtbuddy setup tunnel --iface awg1 /path/to/awg1.conf
# Egress через VPN share-link — чистый, трудноблокируемый upstream для прыжка прокси→Telegram.
# vless:// vmess:// trojan:// ss:// -> локальный sing-box TUN-туннель (type=tunnel; VLESS-Reality
# маскирует прыжок под настоящий TLS).
# wireguard:// -> нативный kernel-WG туннель (как `setup tunnel`).
# несколько ссылок -> пул с автопереключением (urltest).
sudo mtbuddy setup egress 'vless://...@host:443?security=reality&pbk=...&sni=...&flow=xtls-rprx-vision'
# vless / vmess / trojan / ss / hysteria2 — hysteria2 is QUIC, so measure UDP to the endpoint first
sudo mtbuddy setup egress 'wireguard://<privkey>@host:51820?publickey=...&address=10.0.0.2/32'
# IPv6 hopping
sudo mtbuddy ipv6-hop --check
sudo mtbuddy ipv6-hop --auto --prefix 2a01:abcd:ef00:: --threshold 5
# Обновить Cloudflare DNS A record
sudo mtbuddy update-dns 1.2.3.4 proxy.example.com
# Помощь
mtbuddy --help
mtbuddy --lang ru --help
Управление сервисом
sudo systemctl status mtproto-proxy
sudo journalctl -u mtproto-proxy -f
sudo systemctl reload mtproto-proxy
sudo systemctl restart mtproto-proxy
Маршрутизация upstream
Прокси поддерживает несколько способов маршрутизации исходящих соединений к Telegram DC.
[upstream].type | Как работает | Когда использовать |
|---|---|---|
auto | Direct egress без policy mark | Большинство установок |
direct | Прямое соединение с Telegram DC | DC доступны с сервера |
tunnel | SO_MARK=200 и policy routing через VPN tunnel pool | DC заблокированы провайдером |
socks5 | Внешний SOCKS5 proxy с опциональной auth | Уже есть proxy-инфраструктура |
http | HTTP CONNECT proxy с Basic auth | Corporate proxy environments |
VPN tunnel pool
Если Telegram DC заблокированы на уровне сети, можно вести proxy-трафик через пул VPN-туннелей с явным socket policy routing. Прокси работает в host namespace; только сокеты с SO_MARK=200 идут через table 200. mtbuddy держит table 200 на первом живом туннеле из заданного порядка.
Поддерживаемые типы:
- AmneziaWG — DPI-resistant fork WireGuard, основной вариант для Russia/Iran.
- WireGuard — стандартный WireGuard (planned).
Client → mtproto-proxy (host namespace)
│
SO_MARK=200
│
Linux policy routing table 200
│
awg0 / awg1 / ... (pool)
│
Telegram DC servers
sudo mtbuddy setup tunnel /path/to/awg0.conf
sudo mtbuddy setup tunnel 'vpn://...'
sudo mtbuddy setup tunnel --iface awg1 /path/to/awg1.conf
В интерактивном меню mtbuddy настройка туннеля сначала спрашивает тип VPN (пока доступен только AmneziaWG), затем показывает текущий пул. Выберите Создать новый туннель, чтобы добавить следующий свободный awgN, или выберите существующий интерфейс, чтобы заменить конфиг этого участника пула.
Конфиги, которые mtbuddy не создавал, не удаляются. mtbuddy пишет в
/etc/amnezia/amneziawg/<iface>.conf — в собственный каталог AmneziaWG, где лежат и ваши конфиги, —
поэтому в каждый созданный им конфиг добавляется шапка # Generated by mtbuddy, и только такие
удаляются при mtbuddy uninstall и tunnel delete. Если указать --iface awg1 на уже существующий
конфиг интерфейса, mtbuddy принимает его как есть: нормализует файл (убирает DNS, добавляет
Table = off) и добавляет имя в пул, но при удалении не трогает ни файл, ни поднятый интерфейс. Всё,
что осталось, перечисляется путями в конце uninstall — решать вам.
mtbuddy не трогает [general].use_middle_proxy, а настраивает только transport ([upstream].type = "tunnel"). После setup он ставит mtproto-tunnel-pool.timer, проверяет mark 200 к Telegram DC и печатает команды для эксплуатации. Контроллер пула пробует Telegram через каждый туннель и делает ip route replace; автоматический failover не перезапускает mtproto-proxy.
Пример конфига:
[upstream]
type = "tunnel"
[upstream.tunnel]
interface = "awg0" # legacy / fallback
interfaces = ["awg0", "awg1"]
pinned_interface = "" # пусто = priority auto-failback
SOCKS5 proxy
[upstream]
type = "socks5"
[upstream.socks5]
host = "127.0.0.1"
port = 1080
username = "admin" # optional
password = "secret"
HTTP CONNECT proxy
[upstream]
type = "http"
[upstream.http]
host = "127.0.0.1"
port = 8080
username = "admin" # optional
password = "secret"
Важно: через upstream маршрутизируется трафик к DC и refresh MiddleProxy metadata (
getProxyConfig/getProxySecret). Mask/camouflage-соединения всегда идут напрямую.
О зависимостях: «ноль зависимостей» верно для дефолтного
auto/direct. В режимахsocks5,httpилиtunnelrefresh метаданных MiddleProxy вызываетcurl, поэтомуcurlдолжен быть установлен на хосте (штатный установщик ставит его сам).
Конфигурация
Конфиг находится в /opt/mtproto-proxy/config.toml. mtbuddy создаёт его при установке; можно редактировать вручную и перезапускать сервис.
[general]
use_middle_proxy = true
[upstream]
type = "auto" # auto | direct | tunnel | socks5 | http
# allow_direct_fallback = false
[server]
port = 443
# public_ip = "proxy.example.com" # входящий IP/domain для клиентских ссылок
# public_port = 443 # порт в ссылках при HAProxy/Nginx
# middle_proxy_nat_ip = "203.0.113.10" # переопределить исходящий IPv4 для Telegram MiddleProxy (иначе авто-детект через egress)
max_connections = 512
# workers = 1 # SO_REUSEPORT epoll-воркеры: 1 = однопоточно (дефолт); 0 = по числу CPU; N распределяет нагрузку по ядрам
idle_timeout_sec = 120
# client_silence_close_sec = 0 # Закрывать релей, где ответ сервера остался без ответа клиента N сек (ломает iOS-затык на bad_salt, где «обновление» висит ~90-120с) → мгновенный чистый реконнект. 0 = выкл; best-effort, изредка закрывает и здоровый коннект — ~10-15 если включаете
handshake_timeout_sec = 15
# dc_connect_timeout_sec = 10 # Дедлайн TCP-connect до эндпоинта Telegram DC; быстро отбрасывает заблэкхоленный эндпоинт, чтобы failover перешёл к следующему (в рамках handshake_timeout_sec). 0 = выкл; на здоровые коннекты (<1с) не влияет
graceful_shutdown_timeout_sec = 15
log_level = "info"
rate_limit_per_subnet = 0 # 0 = выключено (по умолчанию; не ложно-срабатывает на carrier-NAT). Для не-NAT хостов задайте напр. 30
handshake_flood_guard_enabled = false
handshake_flood_guard_threshold = 20
handshake_flood_guard_window_sec = 30
handshake_flood_guard_block_sec = 120
tag = "" # Optional: promotion tag from @MTProxybot
[censorship]
tls_domain = "rutube.ru"
mask = true
# mask_target = "host.docker.internal" # Optional: custom masking backend host для Docker/remote Nginx
mask_port = 8443 # 8443 = локальный Nginx (так ставит mtbuddy); 443 = фронт реального tls_domain (опционально, только домены с одноходовым x25519)
fast_mode = true
drs = true
[access.users]
alice = "00112233445566778899aabbccddeeff"
bob = "ffeeddccbbaa99887766554433221100"
[access.direct_users]
alice = true
Ключевые параметры:
| Key | Default | Описание |
|---|---|---|
[upstream].type | auto | auto, direct, tunnel, socks5, http |
[upstream] allow_direct_fallback | false | Разрешить fallback на direct для socks5/http |
[upstream.tunnel] interface | "awg0" | Legacy single interface / fallback |
[upstream.tunnel] interfaces | ["awg0"] | Ordered tunnel pool |
[upstream.tunnel] pinned_interface | — | Ручной preferred interface, если он жив |
[general] use_middle_proxy | false | ME mode для DC1..5 |
[server] port | 443 | TCP listen port |
[server] public_ip | auto | Входящий IP/domain для клиентских ссылок |
[server] public_port | [server].port | Порт для клиентских ссылок, если публичный порт отличается от listen-port |
[server] middle_proxy_nat_ip | auto | Исходящий IPv4 для MiddleProxy key derivation; авто-детект через настроенный egress (direct → IP хоста, socks5/http → exit-IP прокси, tunnel → exit-IP туннеля), поэтому SOCKS/туннель + ad-tag работают из коробки. Задавайте явно только для переопределения, если авто-детект не видит реальный egress |
[server] max_connections | 512 | Лимит одновременных соединений |
[server] workers | 1 | SO_REUSEPORT epoll-воркеры: 1 = однопоточно; 0 = по числу CPU; N распределяет нагрузку relay/crypto по ядрам. При >1 перезагрузка конфига по SIGHUP требует рестарта |
[server] middleproxy_buffer_kb | 1024 | Буфер MiddleProxy на соединение |
[server] tag | — | 32-hex promotion tag от @MTProxybot |
[server] rate_limit_per_subnet | 0 | Лимит новых соединений/сек на /24 (IPv4) или /48 (IPv6). 0 = выключено (по умолчанию, NAT-friendly); для не-NAT хостов задайте напр. 30 |
[server] handshake_flood_guard_enabled | false | Временно отклонять IP, которые часто не проходят MTProto handshake (по умолчанию выключен — безопасно для NAT/VPN) |
[server] handshake_flood_guard_threshold | 20 | Число плохих handshake/rate/budget событий с одного IP до временного deny |
[server] handshake_flood_guard_window_sec | 30 | Окно подсчёта для handshake_flood_guard_threshold |
[server] handshake_flood_guard_block_sec | 120 | Длительность временного deny для шумного IP |
[server] idle_timeout_jitter_pct | 15 | Джиттер ±% на idle-таймаут соединения, чтобы константа не была сигнатурой (0 — выключить) |
[server] dc_connect_timeout_sec | 10 | Per-endpoint дедлайн TCP-connect до эндпоинта Telegram DC. Заблэкхоленный эндпоинт не шлёт RST, и ядро держит SYN_SENT ~2 мин; handshake_timeout_sec уже ограничивает весь handshake, но глобально, поэтому медленный первый эндпоинт съедает бюджет failover для остальных. Это быстро отбрасывает один мёртвый эндпоинт и переходит к следующему (в пределах handshake_timeout_sec). Держите ниже handshake_timeout_sec. 0 = выкл; здоровые коннекты (<1с) не затрагиваются |
[server] client_silence_close_sec | 0 | Закрывать установленный релей, где последний ответ сервера остался без ответа клиента N секунд — это даёт мгновенный (~450мс) чистый реконнект. Ломает затык iOS MtProtoKit: после отказа по протухшей соли клиент выбрасывает соль и перестаёт слать, и «обновление» висит ~90-120с, пока DC сам не закроет сокет. Срабатывает только когда последним по проводу был s2c (здоровый idle-коннект, чьё последнее слово — свой пинг/ack, не трогается). Best-effort, не полное лечение: значение ниже самого медленного легитимного ответа изредка закроет и здоровый коннект (лишний ~450мс реконнект). 0 = выкл (дефолт); если включаете, ~10–15 разумная отправная точка, дальше под себя |
[censorship] tls_domain | "google.com" | Домен для TLS-маскировки |
[censorship] mask | true | Forward invalid clients на tls_domain |
[censorship] unknown_sni_action | "mask" | ClientHello с чужим SNI: mask (forward), reject (фатальный TLS-alert, как отклоняющий сервер) или drop |
[censorship] mask_target | unset | Optional backend host для masked clients |
[censorship] mask_port | 443 | Local masking port (8443 для Nginx zero-RTT) |
[censorship] fast_mode | false | Делегировать S2C encryption DC |
[web] enabled | false | Включить релей WEB-прокси (Telegram Desktop 7.1+) |
[web] only | false | Отдавать только WEB: прямой MTProto маскируется для всех, кроме релея. Требует mask = true; игнорируется без enabled |
[web] domain | unset | Публичный хост в ссылках tg://webproxy — неизменяем после раздачи ссылок |
[web] listen | "127.0.0.1" | Адрес релея (TLS терминируется перед ним) |
[web] port | 8081 | Порт релея |
[web] backend | 127.0.0.1:<server.port> | MTProxy, куда релей дозванивается на каждый поток |
[web] mask_backend | не задан | Терминатор с PROXY-протоколом для masked-соединений к domain — чтобы реальный IP клиента пережил masking-хоп |
[web] cert / [web] key | не задан | Свой сертификат для vhost релея вместо Let's Encrypt (читается mtbuddy setup web) |
[web] ws_path | "/api/v1/socket" | Same-origin WebSocket-эндпоинт страницы-моста |
[web] trust_forwarded_for | true | Брать адрес клиента из forwarded-заголовка (только самая правая запись) |
[web] client_ip_header | "x-forwarded-for" | Какой заголовок его несёт (cf-connecting-ip за Cloudflare) |
[web] check_origin | true | Требовать Origin: https://<domain> при upgrade |
[web] max_sessions | 64 | Одновременных десктоп-клиентов |
[web] max_streams | 32 | Логических MTProto-сокетов на клиента |
[web] relay_sources | [] | Дополнительные IP, которым разрешён транспорт dd (loopback — всегда) |
[access.users] <name> | — | 32-hex secret на пользователя |
[access.direct_users] <name> | — | Bypass MiddleProxy для пользователя |
[access.user_max_conns] <name> | — | Лимит одновременных соединений на пользователя (меняется рестартом) |
[access.user_expirations] <name> | — | Дата истечения доступа "YYYY-MM-DD" для пользователя (меняется рестартом) |
[access.user_max_ips] <name> | — | Лимит одновременно используемых ссылкой IP-адресов (IPv4-адрес / IPv6 /64) на пользователя (меняется рестартом) |
Secret можно сгенерировать через
mtbuddy secretилиopenssl rand -hex 16.Ссылки печатаются командой
sudo mtbuddy links: по умолчанию показываются только FakeTLS (ee...domain) ссылки; secure padded (dd...) варианты выводятся, когда включён транспортdd(fake_tls_only = false). Runtime-логи намеренно скрывают secrets и proxy links.Транспорт
dd(«secure»/padded) по умолчанию отключён ([censorship].fake_tls_only = true) — это обычный обфусцированный MTProto без TLS-маскировки, который DPI фингерпринтит напрямую как MTProto. По умолчанию прокси принимает только FakeTLS (ee), иmtbuddy linksпечатает толькоee-ссылки. Чтобы раздаватьdd-ссылки (сценарии с низким DPI / совместимость), задайтеfake_tls_only = false. См. THREAT_MODEL.md.Оба стража по умолчанию выключены, чтобы carrier-NAT, VPN-egress и офисные сети (много легитимных клиентов за одним IP/подсетью) не получали ложных блокировок скопом: per-subnet rate limit (
rate_limit_per_subnet = 0) и exact-IP handshake flood guard (handshake_flood_guard_enabled = false). Доступ и так закрыт per-user secret, глобальным handshake-inflight бюджетом иmax_connections. На single-tenant / не-NAT хосте под реальным абьюзом включите: задайтеrate_limit_per_subnet(например30) иhandshake_flood_guard_enabled = true(настройтеhandshake_flood_guard_threshold/ window / block).Оба стража выше работают после
accept()(внутри прокси). Для дополнительного уровня ядра, который дропает абьюзные SYN-всплески до того, как они займут сокет/accept(), есть опциональный лимитер SYN по IP-источнику:sudo mtbuddy setup syn-limit --preset soft. Это правило iptableshashlimit, поставленное как отдельный oneshotmtproto-syn-limit.service, поэтомуCAP_NET_ADMINпрокси не выдаётся. Тоже выключен по умолчанию и с той же оговоркой про carrier-NAT (пресетsoft2/с·burst-5 — безопаснее для CGNAT);--removeдля отката, статус + счётчик дропов вmtbuddy status. Перезапустите после смены порта прокси.
WEB-прокси (Telegram Desktop 7.1+)
В Telegram Desktop 7.1 появился четвёртый тип прокси — WEB. Это обычный MTProxy, у которого
транспорт — браузер: клиент вообще не открывает MTProto-сокет. Скрытый нативный WebView грузит
https://<ваш-домен>/?bridge=<capability>, и страница гоняет мультиплексированные фреймы через
same-origin WebSocket до релея, а тот на каждый логический поток дозванивается до обычного
MTProxy — до этого самого.
Telegram Desktop
→ скрытый WebView (WKWebView / WebView2 / WebKitGTK)
→ https://relay.example.com/ ← настоящий браузерный TLS к настоящему сайту
→ mtproto-web-relay
→ mtproto-proxy
→ Telegram
Цензор видит настоящий браузерный отпечаток, настоящий TLS-хендшейк с сертификатом от реального CA и дальше HTTP — потому что это и есть браузер. Нет ни FakeTLS-ServerHello, который может не совпасть, ни MTProto-хендшейка на проводе.
Установка:
sudo mtbuddy setup web --domain relay.example.com
sudo mtbuddy links # теперь печатает и ссылки tg://webproxy
Нужен домен под вашим контролем с A-записью на этот хост; mtbuddy выпустит сертификат Let's Encrypt
по HTTP-01 (для этого откроется порт 80) и поставит hook на продление.
Свой сертификат, если он уже есть — acme.sh, wildcard, Cloudflare Origin с Cloudflare впереди.
Тогда mtbuddy ничего не выпускает, не ставит hook продления и указывает vhost на ваши файлы; пути
сохраняются в [web], поэтому mtbuddy update их не затирает:
sudo mtbuddy setup web --domain relay.example.com \
--cert /etc/ssl/relay/fullchain.pem --key /etc/ssl/relay/privkey.pem
Сертификат всё равно должен быть таким, который примет WebView клиента: публичный CA — или частный только там, где клиенту его предъявляете не вы, а тот, кто стоит перед вами. Самоподписанный сертификат, отданный клиенту напрямую, WebView отвергнет, и мост не загрузится.
Две топологии, обе поддерживаются:
| Режим | Что происходит | Когда выбирать |
|---|---|---|
--mode mask (по умолчанию) | Релей отдаётся через собственный masking-бэкенд прокси. Прокси на :443 и так пересылает весь не-MTProto TLS в локальный Nginx; второй vhost там, выбираемый по SNI, держит настоящий сертификат вашего домена. Дополнительный публичный порт не нужен. | Один хост, один IP — обычный случай |
--mode behind | Релей слушает обычный HTTP, а TLS терминирует кто-то другой: Cloudflare, второй хост, отдельный IP. | У вас уже есть веб-присутствие, или хочется, чтобы клиент ходил на адреса CDN, а не на ваши |
Как это взаимодействует с остальным прокси:
- Те же пользователи, те же секреты. WEB-ссылка несёт тот же 16-байтный секрет, что и FakeTLS,
но в кодировке
dd…, а неee…: секретee(FakeTLS) Telegram Desktop считает неподдерживаемым для WEB-прокси, потому что релей — сырая труба и не добавляет TLS-emulation запись. Каждый из[access.users]получает обе ссылки; лимиты соединений и сроки доступа работают как обычно.[access.user_max_ips]видит реальные адреса браузеров только с[web].mask_backend— без него каждый WEB-клиент приходит на прокси как127.0.0.1, и такие соединения исключаются из квоты, а не занимают один общий слот. fake_tls_onlyостаётся включённым. Публичный интернет по-прежнему не получаетdd-респондера для фингерпринтинга. Через этот гейт пускается только адрес самого релея (loopback по умолчанию плюс то, что перечислено в[web].relay_sources), и решение принимается по адресу изaccept()— никогда по заголовкуPROXY, который прислал клиент.- WEB-only (
[web].only) идёт дальше: прокси перестаёт отвечать на прямой MTProto вообще. Рукопожатие FakeTLS илиddот кого угодно, кроме релея, уходит в masking-бэкенд по тому же пути, что и неверный секрет, — пробер не отличит одно от другого. Подробнее ниже. - Реальные IP клиентов сохраняются. Релей префиксует каждое соединение к бэкенду
PROXY-protocol-заголовком с адресом браузера, поэтому per-IP учёт, flood guard и сам Telegram видят
настоящего клиента, а не
127.0.0.1. - Устойчивость к активному пробингу. Bridge capability — это
HMAC-SHA256(секрет пользователя), так что гость, который не может предъявить производную от настоящего секрета, страницу-мост не увидит вообще: ему отдаётся та же обычная страница-прикрытие, что и на любом другом пути. - Ёмкость. Один WEB-клиент занимает одно masked-соединение плюс по соединению на каждый
логический MTProto-поток в счёт
[server].max_connections$ — закладывайте примерно 3–5 \times от прямого клиента и поднимайте $max_connections.
Ограничения: только десктоп (7.1+; у Android тот же контракт моста, но UI ещё не выпущен), звонки не поддерживаются, и транспорт чувствительнее к RTT, чем прямое соединение. Это запасной путь на случай, когда прямая ссылка заблокирована, а не замена ей.
Режим WEB-only
Бывает, что сеть блокирует IP ровно в тот момент, когда к нему напрямую подключается клиент Telegram: прокси работает — и следом весь хост перестаёт отвечать по всем TCP-портам на несколько суток. Если у вас так, перестаньте предлагать прямую дверь:
sudo mtbuddy setup web --domain relay.example.com --only
sudo mtbuddy links # теперь печатает только ссылки tg://webproxy
С [web].only data plane отвечает по MTProto релею и никому больше. Все остальные — включая
настоящего клиента Telegram с валидной ee-ссылкой — уходят в masking-бэкенд ровно так же, как при
неверном секрете, поэтому прокси неотличим от сайта, которым прикрывается. mtbuddy links печатает
только WEB-ссылки: остальные больше не подключаются.
Чем это оплачивается:
- Все уже выданные
ee/dd-ссылки перестают работать. Постепенного перехода нет; выключить —sudo mtbuddy setup web --no-only. - Только Desktop 7.1+ — телефоны WEB-ссылку не откроют вообще.
[web].max_sessionsстановится потолком по пользователям, потому что теперь все приходят через релей.- Нужен
[censorship].mask = true. Без сайта-прикрытия отказ выглядит как закрытое соединение — а это для пробера сигнал чище, чем ответ по MTProto; именно поэтомуmtbuddyне даёт включить WEB-only без локального masking-бэкенда.
mtproto_web_only_masked_total считает то, что гейт развернул.
Дашборд мониторинга
Лёгкий web dashboard (~30 МБ RAM) показывает live connections, CPU/RAM, network throughput, proxy stats, состояние tunnel pool/failover, пользователей и streaming logs.
sudo mtbuddy setup dashboard
# Открыть через SSH tunnel
ssh -L 61208:localhost:61208 root@<server_ip>
# → http://localhost:61208
Dashboard требует HTTP Basic auth (имя пользователя: любое; пароль генерируется автоматически в /opt/mtproto-proxy/monitor/dashboard.token — выведите его командой cat на сервере). Это root-привилегированная панель управления, поэтому держите её на loopback/SSH-туннеле и никогда не публикуйте по plain HTTP — только за HTTPS + reverse proxy.
Демо: dashboard
Prometheus metrics
Локальную историю дашборда можно отключить независимо от Prometheus/Grafana:
задайте traffic_history_enabled = false в существующей секции [monitor], оставив
[metrics].enabled = true. При следующем опросе (обычно в течение 30 секунд) дашборд прекратит опрос метрик и
запись истории, сохранит базу и покажет, что история отключена. Возврат к true
возобновляет сбор. Первый замер после запуска или возобновления задаёт исходные
счётчики: трафик за паузу не добавляется задним числом.
mtproto-proxy может отдавать Prometheus-compatible endpoint на отдельном порту.
[metrics]
enabled = true
host = "127.0.0.1"
port = 9400
Endpoint plaintext HTTP:
GET /metrics
Docker-пример для метрик:
docker run --rm \
-p 443:443 \
-p 9400:9400 \
-v "$PWD/config.toml:/etc/mtproto-proxy/config.toml:ro" \
mtproto-zig
Локальная сборка
Нужен Zig 0.16.0.
git clone https://github.com/sleep3r/mtproto.zig.git
cd mtproto.zig
make build
make test
make e2e
make fmt
make deploy
make dashboard
# optional
zig build bench
zig build soak
Кросс-компиляция под Linux с macOS:
zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux -Dcpu=x86_64_v3+aes
scp zig-out/bin/mtproto-proxy root@<SERVER>:/opt/mtproto-proxy/
Docker
Docker поддерживается для тестов, упаковочных экспериментов и простых сценариев, где нужен только бинарник прокси. Основной production-путь проекта — нативный Linux host под управлением mtbuddy: DPI-модули, tunnel pool failover, policy routing, Nginx masking, nfqws и recovery timers являются host-level интеграциями и не представлены контейнером полностью.
docker pull ghcr.io/sleep3r/mtproto.zig:latest
docker run --rm \
-p 443:443 \
-v "$PWD/config.toml:/etc/mtproto-proxy/config.toml:ro" \
ghcr.io/sleep3r/mtproto.zig:latest
MiddleProxy для медиа/промо чувствителен к исходному source IP:port, который попадает в encrypted handshake. Исходящий IPv4 авто-детектится через настроенный upstream — direct зондирует хост, socks5/http зондирует через прокси (узнаёт exit-IP SOCKS), tunnel — через интерфейс туннеля, — поэтому SOCKS/туннель + ad-tag работают без ручной настройки. Для Docker-деплоя с MiddleProxy лучше использовать host networking (--network host) или нативный mtbuddy install. [server].public_ip — только входящий адрес для клиентов; задавайте [server].middle_proxy_nat_ip лишь для переопределения, если авто-детект не видит реальный egress. Bridge или удаленный NAT, переписывающий source port, всё равно может ломать MiddleProxy handshake.
Локальная сборка:
docker build -t mtproto-zig .
docker buildx build --platform linux/amd64,linux/arm64 -t your-registry/mtproto-zig:latest --push .
Для production censorship-bypass deployments лучше использовать
mtbuddy install. OS-level mitigations (iptables TCPMSS, nfqws, tunnel policy routing, masking/recovery units) внутри контейнера не применяются; в контейнере запускается только proxy binary.
Доверие и безопасность
- SECURITY.md — vulnerability reporting policy.
- THREAT_MODEL.md — security goals, non-goals, adversary model, residual risks.
- CONTRIBUTING.md — dev workflow и PR expectations.
- CHANGELOG.md — история релизов.
- LICENSE — MIT license.
Ограничения и совместимость
Полная модель описана в THREAT_MODEL.md. Кратко:
- Известные ограничения
- Это transport-hardening proxy, а не анонимная сеть.
- Качество обхода может меняться по мере развития DPI.
- Dashboard/metrics по умолчанию plaintext; не публикуйте их без auth/TLS.
- Звонки Telegram через этот прокси не работают. Звонки требуют SOCKS-style path, который не вписывается в MTProto/TLS masking.
- Без MiddleProxy (
[general].use_middle_proxy = true) медиа на non-Premium аккаунтах не будут грузиться.
- Региональные caveats
- Поведение провайдеров отличается по странам и регионам.
- IPv6/AAAA сильно зависят от провайдера и могут влиять на iOS/Desktop latency.
- Tunnel routing зависит от host policy routing и разрешённых VPN-протоколов.
- Telegram clients
- Official Telegram Android/iOS/Desktop: ожидается работа на актуальных версиях.
- Third-party clients: best effort.
- OS/kernel
- Linux
x86_64: supported. - Linux
aarch64: supported. - Docker on Linux: supported with caveats.
- macOS/Windows runtime: not supported.
- Linux
- Что может сломаться после изменений Telegram/DC
- MiddleProxy metadata и endpoint behavior.
- handshake expectations новых клиентов.
- DC/media routing edge cases.
Troubleshooting — застряло на "Updating..."
1. Есть AAAA record, но IPv6 на сервере не работает. DNS отдаёт AAAA, iOS пробует IPv6 первым, получает timeout и медленно падает на IPv4. Решение: убрать AAAA, пока IPv6 routing не настроен полностью.
dig +short proxy.example.com AAAA
ip -6 route
2. Домашний Wi-Fi блокирует IPv4 сервера. Мобильные сети часто работают, потому что используют IPv6. Домашние роутеры могут блокировать destination IPv4. Решение: включить IPv6 Prefix Delegation (IA_PD) на роутере.
3. VPN режет MTProto traffic. Коммерческие VPN часто DPI'ят и дропают proxy traffic. Решение: сменить VPN protocol или использовать self-hosted AmneziaWG.
4. WireGuard/Docker на том же сервере.
Docker bridge может дропать пакеты из VPN subnet.
Решение: iptables -I DOCKER-USER -s 172.29.172.0/24 -p tcp --dport 443 -j ACCEPT
5. DC203 media resets на non-premium clients. Проверьте логи:
journalctl -u mtproto-proxy | grep -E "dc=203|Middle"
Прокси обновляет DC203 metadata с Telegram на старте. Если core.telegram.org недоступен, используются bundled fallback addresses.
При [upstream].type = "socks5" или "http" metadata refresh идёт через этот upstream; проверьте путь командой sudo mtbuddy config doctor --network.
License
MIT © 2026 Aleksandr Kalashnikov