Skip to content

sleep3r/mtproto.zig

Repository files navigation

mtproto.zig

High-performance Telegram MTProto proxy written in Zig

Disguises Telegram traffic as standard TLS 1.3 HTTPS to bypass network censorship.

177 KB binary · Sub-1 MB RAM · Boots in <10 ms · Zero dependencies

License: MIT Zig Platform


Why this one? · Install · Update · Commands · Routing · Config · Dashboard · Build · Docker · Trust · Compatibility · FAQ


Why this one?

Most MTProto proxies are large, dependency-heavy, and use lots of memory. This one is different:

Proxy Language Binary Baseline RSS Startup Dependencies
mtproto.zig Zig 177 KB 0.75 MB < 10 ms 0
Official MTProxy C 524 KB 8.0 MB < 10 ms openssl, zlib
Telemt Rust 15 MB 12.1 MB ~ 5-6 s 423 crates
mtg Go 13 MB 11.6 MB ~ 30 ms 78 modules
MTProtoProxy Python N/A ~ 30 MB ~ 300 ms python3, cryptography
JSMTProxy Node.js N/A ~ 45 MB ~ 400 ms nodejs, openssl

Why Zig?

We chose Zig because it provides the raw performance and micro-footprint of C, but without the memory unsafety or build-system nightmares:

  • No arbitrary allocations: All connection slots and buffers are pre-allocated on startup. There is no garbage collector dropping frames under heavy load.
  • Hermetic cross-compilation: Run zig build on macOS, and out comes a statically linked Linux binary. No Docker, no glibc version mismatches.
  • Comptime: Costly operations like protocol definition mapping, endianness conversions, and bilingual string lookup for mtbuddy are resolved during compilation, giving instant startup times.

It also ships more evasion techniques than any of the above:

Technique What it does
Fake TLS 1.3 Connections look like normal HTTPS to DPI
DRS Mimics Chrome/Firefox TLS record sizes
Zero-RTT masking Local Nginx serves real TLS responses to active probes, defeating timing analysis
TCPMSS=88 Fragments ClientHello across 6 TCP packets, breaking DPI reassembly
nfqws TCP desync Sends fake packets + TTL-limited splits to confuse stateful DPI
Split-TLS 1-byte Application records to defeat passive signatures
VPN tunnel Routes through WireGuard/AmneziaWG using explicit socket policy routing (SO_MARK) when DCs are blocked
IPv6 hopping Auto-rotates IPv6 address from /64 on ban detection via Cloudflare API
Anti-replay Rejects replayed handshakes + detects ТСПУ Revisor active probes
Multi-user Independent per-user secrets
MiddleProxy ME transport with auto-refreshed Telegram metadata

Install

All installation, updates, and management are done through mtbuddy — a native Zig CLI that ships alongside the proxy.

One command

curl -fsSL https://raw.githubusercontent.com/sleep3r/mtproto.zig/main/deploy/bootstrap.sh | sudo bash

# Explicitly allow unsigned bootstrap mode (not recommended)
curl -fsSL https://raw.githubusercontent.com/sleep3r/mtproto.zig/main/deploy/bootstrap.sh | sudo bash -s -- --insecure
# or: MTPROTO_INSECURE=1

This downloads the latest mtbuddy binary, verifies minisign signature + SHA-256 checksum from the GitHub Release, and runs mtbuddy --help. Then install the proxy:

# Minimal — auto-generates a secret, enables all DPI bypass modules
sudo mtbuddy install --port 443 --domain wb.ru --yes

# Bring your own secret and username
sudo mtbuddy install --port 443 --domain wb.ru --secret <32-hex> --user alice --yes

# Disable all DPI modules (bare proxy only)
sudo mtbuddy install --port 443 --domain wb.ru --no-dpi --yes

# Install using an existing config file (auto-maps port and domain)
sudo mtbuddy install --config /path/to/config.toml --yes

# Explicitly allow unsigned mode (not recommended)
sudo mtbuddy install --insecure --port 443 --domain wb.ru --yes

At the end, mtbuddy prints a ready-to-use tg:// connection link.

Interactive wizard

If you prefer to be walked through the setup:

sudo mtbuddy --interactive
Demo: interactive installer

Demo: interactive installer


What the install does

  1. Downloads the pre-built proxy binary from GitHub Releases (auto-detects CPU: x86_64_v3x86_64aarch64)
  2. Generates a random secret (or uses --secret)
  3. Creates a systemd service (mtproto-proxy)
  4. Opens the port in ufw (if active)
  5. Applies TCPMSS=88 iptables rules
  6. Sets up Nginx masking + nfqws TCP desync (unless --no-dpi)
  7. Prints tg:// link

Install options

Flag Default Description
--port, -p 443 Proxy listen port
--domain, -d wb.ru TLS masking domain
--secret, -s auto User secret (32 hex chars)
--user, -u user Username in config.toml
--config, -c Use existing config.toml file
--yes, -y Skip confirmation prompt
--max-connections <N> 512 Max proxy connections
--bind, -b Bind to specific IP (default: all interfaces)
--no-masking Disable Nginx masking
--no-nfqws Disable nfqws TCP desync
--no-tcpmss Disable TCPMSS=88
--no-dpi Disable all DPI modules
--middle-proxy Enable Telegram MiddleProxy relay
--ipv6-hop Enable IPv6 auto-hopping
--version, -v <tag> latest Release version to install
--insecure Allow unsigned assets (not recommended)

Update

# Update to latest release (verifies minisign + checksum, checks CPU compat, auto-rollback on failure)
sudo mtbuddy update

# Pin to a specific version
sudo mtbuddy update --version v0.11.1

# Explicitly allow unsigned mode (not recommended)
sudo mtbuddy update --insecure

Other mtbuddy commands

# Show proxy and module status
sudo mtbuddy status

# Validate and inspect config
sudo mtbuddy config validate
sudo mtbuddy config doctor
sudo mtbuddy config print-effective

# Print Telegram proxy links from config.toml (sensitive output)
sudo mtbuddy links
sudo mtbuddy links --server proxy.example.com --config /opt/mtproto-proxy/config.toml

# Generate a fresh 32-hex user secret
mtbuddy secret

# Hot-reload config (SIGHUP, reloadable settings only)
sudo mtbuddy reload

# Setup DPI modules after the fact
sudo mtbuddy setup masking --domain wb.ru
sudo mtbuddy setup nfqws
sudo mtbuddy setup recovery

# Install web monitoring dashboard
sudo mtbuddy setup dashboard

# VPN tunnel (for servers where Telegram DCs are blocked)
sudo mtbuddy setup tunnel /path/to/awg0.conf
sudo mtbuddy setup tunnel 'vpn://...'

# IPv6 hopping
sudo mtbuddy ipv6-hop --check
sudo mtbuddy ipv6-hop --auto --prefix 2a01:abcd:ef00:: --threshold 5

# Update Cloudflare DNS A record
sudo mtbuddy update-dns 1.2.3.4

# Full help
mtbuddy --help
mtbuddy --lang ru --help

Service management

sudo systemctl status mtproto-proxy
sudo journalctl -u mtproto-proxy -f
sudo systemctl reload mtproto-proxy   # SIGHUP hot-reload (where possible)
sudo systemctl restart mtproto-proxy

Upstream Routing

The proxy supports multiple ways to route outgoing connections to Telegram DC servers.

Routing modes

[upstream].type How it works When to use
auto (default) Direct egress without tunnel policy marks Most deployments
direct Connect to Telegram DCs directly from the host DCs reachable from the server
tunnel Direct connect with SO_MARK=200 policy-routed via VPN interface DCs blocked by the ISP
socks5 Route through an external SOCKS5 proxy with optional auth Existing proxy infrastructure
http Route through an HTTP CONNECT proxy with optional auth Corporate proxy environments

VPN tunnel

If your VPS is in a region where Telegram DCs are blocked at the network level, you can route proxy traffic through a VPN tunnel with explicit socket policy routing. The proxy runs in the host namespace; only sockets marked by the proxy (SO_MARK=200) are routed through the tunnel table.

Currently supported VPN types:

  • AmneziaWG — DPI-resistant WireGuard fork (recommended for Russia/Iran)
  • WireGuard — standard WireGuard (planned)
Client → mtproto-proxy (host namespace)
                     │
                SO_MARK=200
                     │
        Linux policy routing table 200
                     │
                 awg0 (tunnel)
                     │
             Telegram DC servers
sudo mtbuddy setup tunnel /path/to/awg0.conf
# or paste an Amnezia share link directly
sudo mtbuddy setup tunnel 'vpn://...'

mtbuddy keeps [general].use_middle_proxy unchanged and only configures transport ([upstream].type = "tunnel"). After setup, it validates policy routes (mark 200) to Telegram DC ranges and prints operational commands.

You can also explicitly configure the tunnel interface in config.toml:

[upstream]
type = "tunnel"

[upstream.tunnel]
interface = "awg0"

SOCKS5 proxy

Route DC connections through an external SOCKS5 proxy. Supports RFC 1928 auth.

[upstream]
type = "socks5"

[upstream.socks5]
host = "127.0.0.1"
port = 1080
username = "admin"    # optional, omit for no-auth
password = "secret"

HTTP CONNECT proxy

Route DC connections through an HTTP CONNECT proxy. Supports Basic auth.

[upstream]
type = "http"

[upstream.http]
host = "127.0.0.1"
port = 8080
username = "admin"    # optional, omit for no-auth
password = "secret"

Note: Only DC-bound traffic is routed through the configured upstream. Mask (camouflage) connections always go direct.


Configuration

Config lives at /opt/mtproto-proxy/config.toml. MTBuddy generates it on install; you can edit it manually and restart:

[general]
use_middle_proxy = true   # ME mode for promo-channel parity

[upstream]
type = "auto"            # auto | direct | tunnel | socks5 | http
# allow_direct_fallback = false   # fail-closed by default for socks5/http misconfig

[server]
port = 443
# public_ip = "proxy.example.com"   # Override auto-detected IP (recommended with tunnel)
max_connections = 512
idle_timeout_sec = 120
handshake_timeout_sec = 15
graceful_shutdown_timeout_sec = 15
log_level = "info"        # debug | info | warn | err
rate_limit_per_subnet = 30
tag = ""                  # Optional: promotion tag from @MTProxybot

[censorship]
tls_domain = "wb.ru"
mask = true
mask_port = 8443          # 8443 for local Nginx zero-RTT masking
fast_mode = true          # Recommended: delegates S2C AES to the DC, saves CPU/RAM
drs = true                # Dynamic Record Sizing (mimics Chrome/Firefox)

[access.users]
alice = "00112233445566778899aabbccddeeff"
bob   = "ffeeddccbbaa99887766554433221100"

[access.direct_users]
alice = true   # bypass MiddleProxy for this user
Full configuration reference
Key Default Description
[upstream].type auto Egress mode: auto (direct), direct, tunnel (VPN via socket policy routing), socks5, or http
[upstream] allow_direct_fallback false If true, allows socks5/http modes to fall back to direct egress when upstream is unavailable
[upstream.tunnel] interface "awg0" Name of the VPN network interface for SO_MARK routing
[upstream.socks5] host SOCKS5 proxy address
[upstream.socks5] port SOCKS5 proxy port
[upstream.socks5] username SOCKS5 username (empty = no auth)
[upstream.socks5] password SOCKS5 password
[upstream.http] host HTTP CONNECT proxy address
[upstream.http] port HTTP CONNECT proxy port
[upstream.http] username HTTP proxy username (empty = no auth)
[upstream.http] password HTTP proxy password
[general] use_middle_proxy false ME mode for DC1..5 (recommended for promo parity)
[general] ad_tag Alias for [server].tag
[server] port 443 TCP listen port
[server] bind_address Specific IP to bind the listen socket (default: all interfaces)
[server] public_ip auto Override auto-detected IP/domain. Required with VPN tunnel; set IPv4 explicitly if clients fail on IPv6 links
[server] backlog 4096 TCP listen queue depth
[server] max_connections 512 Concurrent connection cap, auto-clamped by RAM and RLIMIT_NOFILE
[server] idle_timeout_sec 120 Connection idle timeout
[server] handshake_timeout_sec 15 Handshake completion timeout
[server] graceful_shutdown_timeout_sec 15 SIGTERM drain timeout before force-close
[server] middleproxy_buffer_kb 1024 ME per-connection buffer (KiB). Below 1024 may cause overflow on media traffic
[server] tag 32 hex-char promotion tag from @MTProxybot
[server] log_level "info" debug / info / warn / err
[server] rate_limit_per_subnet 30 Max new conns/sec per /24 (IPv4) or /48 (IPv6). Set 0 to disable
[server] unsafe_override_limits false Disable auto-clamping of max_connections
[monitor] host "127.0.0.1" Dashboard bind address
[monitor] port 61208 Dashboard port
[metrics] enabled false Enable embedded Prometheus /metrics endpoint
[metrics] host "127.0.0.1" Metrics bind address
[metrics] port 9400 Metrics port
[censorship] tls_domain "google.com" Domain to impersonate
[censorship] mask true Forward unauthenticated clients to tls_domain
[censorship] mask_port 443 Local masking port (use 8443 for Nginx zero-RTT)
[censorship] desync true Split-TLS: 1-byte Application records
[censorship] drs false Dynamic Record Sizing
[censorship] fast_mode false Delegate S2C encryption to DC (recommended)
[access.users] <name> 32 hex-char secret per user
[access.direct_users] <name> Bypass ME for this user

Generate a secret: mtbuddy secret or openssl rand -hex 16

Print client links explicitly: sudo mtbuddy links. Runtime proxy logs intentionally hide secrets and proxy links.


Monitoring dashboard

A lightweight web dashboard (~30 MB RAM) shows live connections, CPU/memory, network throughput, proxy stats, tunnel metrics, user management, and streaming logs.

The dashboard is embedded directly into the mtbuddy binary — no extra files needed.

# Install the dashboard on the server
sudo mtbuddy setup dashboard

# Open via SSH tunnel (binds to 127.0.0.1:61208 by default)
ssh -L 61208:localhost:61208 root@<server_ip>
# → http://localhost:61208

Alternatively, expose the dashboard port via [monitor] config section and access directly.

Demo: monitoring dashboard

Demo: monitoring dashboard



Prometheus metrics

mtproto-proxy can expose an embedded Prometheus-compatible metrics endpoint on a dedicated port.

For a complete Docker-based monitoring stack with mtproto-zig, Prometheus, Grafana, and an importable dashboard, see hack/docker/README.md.

[metrics]
enabled = true
host = "127.0.0.1"
port = 9400

The endpoint is plaintext HTTP and serves:

GET /metrics

Typical Docker usage:

docker run --rm \
  -p 443:443 \
  -p 9400:9400 \
  -v "$PWD/config.toml:/etc/mtproto-proxy/config.toml:ro" \
  mtproto-zig

It exposes proxy counters plus process metrics such as RSS, virtual memory, CPU time, and open file descriptors.


Building locally

Requires Zig 0.16.0.

git clone https://github.com/sleep3r/mtproto.zig.git
cd mtproto.zig

make build      # cross-compile ReleaseFast binaries for Linux x86_64_v3+aes
make test       # run Zig tests
make e2e        # run E2E/integration harness
make fmt        # format Zig sources
make deploy     # build + deploy to SERVER (see Makefile)
make dashboard  # SSH tunnel for web dashboard (localhost:61208)

# optional performance checks
zig build bench
zig build soak

Release builders can override the default pinned minisign key if needed:

zig build -Dminisign_pubkey=RW... -Doptimize=ReleaseFast -Dtarget=x86_64-linux

Cross-compile for Linux from 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 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

Build locally:

docker build -t mtproto-zig .
# multi-arch
docker buildx build --platform linux/amd64,linux/arm64 -t your-registry/mtproto-zig:latest --push .

Published linux/amd64 images are built with a portable CPU profile (-Dcpu=x86_64) to avoid Illegal instruction crashes on older VPS CPUs.

OS-level mitigations (iptables TCPMSS, nfqws, etc.) are not applied inside the container; only the proxy binary runs there.


Trust & Security

Repository governance:


Known Limitations & Compatibility

For a full model see THREAT_MODEL.md. Quick operational summary:

  • Known limitations
    • This is a transport-hardening proxy, not an anonymity network.
    • Bypass quality can degrade as DPI strategies evolve.
    • Dashboard/metrics are plaintext by default; do not expose publicly without auth/TLS.
  • Region-specific caveats
    • ISP behavior differs by country/region; configs are not universally portable.
    • IPv6 and AAAA handling vary heavily across providers and can impact iOS/Desktop connection latency.
    • Tunnel routing depends on host policy routing and allowed VPN protocols in that region.
  • Telegram client compatibility
    • Official Telegram Android/iOS/Desktop: expected to work on current releases.
    • Third-party clients: best effort only.
  • Kernel/OS compatibility matrix
    • Linux x86_64: supported (primary target)
    • Linux aarch64: supported
    • Docker on Linux: supported with caveats (OS-level DPI modules are host-side)
    • macOS/Windows runtime: not supported (Linux runtime target only)
  • What can break after Telegram/DC changes
    • MiddleProxy metadata and endpoint behavior
    • handshake expectations in newer Telegram clients
    • DC/media routing edge cases (for example DC203 behavior)

Troubleshooting — stuck on "Updating..."

1. AAAA record exists but IPv6 doesn't work on the server. DNS has an AAAA → iOS tries IPv6 first → timeout → slow fallback to IPv4. Fix: remove AAAA until IPv6 routing is fully configured.

dig +short proxy.example.com AAAA
ip -6 route

2. Home Wi-Fi blocks the server's IPv4. Mobile networks usually work (they use IPv6). Home routers often block the destination IPv4. Fix: enable IPv6 Prefix Delegation (IA_PD) on your router.

3. VPN is dropping MTProto traffic. Commercial VPNs often DPI and drop proxy traffic. Fix: switch VPN protocol, or use a self-hosted AmneziaWG.

4. Co-located WireGuard/Docker on the same server. Docker's bridge drops packets from VPN subnet. Fix: iptables -I DOCKER-USER -s 172.29.172.0/24 -p tcp --dport 443 -j ACCEPT

5. DC203 media resets on non-premium clients. Check logs: journalctl -u mtproto-proxy | grep -E "dc=203|Middle". The proxy auto-refreshes DC203 metadata from Telegram on startup. If core.telegram.org is unreachable, it uses bundled fallback addresses.


License

MIT © 2026 Aleksandr Kalashnikov

About

High-performance Telegram proxy with DPI evasion

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Packages

 
 
 

Contributors