High-performance Telegram MTProto proxy written in Zig
Disguises Telegram traffic as standard TLS 1.3 HTTPS to bypass network censorship.
126 KB binary. ~120 KB RAM. Boots in <2 ms. Zero dependencies.
Features • Quick Start • Deploy • Configuration • Troubleshooting
| Feature | Description | |
|---|---|---|
| TLS 1.3 | Fake Handshake | Connections are indistinguishable from normal HTTPS to DPI systems |
| MTProto v2 | Obfuscation | AES-256-CTR encrypted tunneling (abridged, intermediate, secure) |
| DRS | Dynamic Record Sizing | Mimics real browser TLS behavior (Chrome/Firefox) to resist fingerprinting |
| Multi-user | Access Control | Independent secret-based authentication per user |
| Anti-replay | Timestamp + Digest Cache | Rejects replayed handshakes outside ±2 min window AND detects ТСПУ Revisor active probes |
| Masking | Connection Cloaking | Forwards unauthenticated clients to a real domain |
| Fast Mode | Zero-copy S2C | Drastically reduces CPU usage by delegating Server-to-Client AES encryption to the DC |
| Promotion | Tag Support | Optional promotion tag for sponsored proxy channel registration |
| IPv6 Hopping | DPI Evasion | Auto-rotates IPv6 from /64 subnet on ban detection via Cloudflare API |
| TCPMSS=88 | DPI Evasion | Forces ClientHello fragmentation across 6 TCP packets, breaking ISP DPI reassembly |
| TCP Desync | DPI Evasion | Integrated zapret (nfqws) OS-level desynchronization (fake packets + TTL spoofing) |
| Split-TLS | DPI Evasion | 1-byte Application-level record chunking to defeat passive DPI signatures |
| Zero-RTT | DPI Evasion | Local Nginx server deployed on-the-fly (127.0.0.1:8443) to defeat active probing timing analysis |
| 0 deps | Stdlib Only | Built entirely on the Zig standard library |
| 0 globals | Thread Safety | Dependency injection -- no global mutable state |
Engineering Notes: For deep technical details, cryptography internals, systemd hardening, and benchmarks, see GEMINI.md (Engineering Notes).
- Zig 0.15.2 or later
# Clone
git clone https://github.com/sleep3r/mtproto.zig.git
cd mtproto.zig
# Build (debug)
make build
# Build (optimized for production)
make release
# Run with default config.toml
make runmake testAll Make targets
| Target | Description |
|---|---|
make build |
Debug build |
make release |
Optimized build (ReleaseFast) |
make run CONFIG=<path> |
Run proxy (default: config.toml) |
make test |
Run unit tests |
make clean |
Remove build artifacts |
make fmt |
Format all Zig source files |
make deploy |
Cross-compile, upload to VPS, restart service |
make deploy SERVER=<ip> |
Deploy to a specific server |
curl -sSf https://raw.githubusercontent.com/sleep3r/mtproto.zig/main/deploy/install.sh | sudo bashThis will:
- Install Zig 0.15.2 (if not present)
- Clone and build the proxy with
ReleaseFast - Generate a random 16-byte secret
- Create a
systemdservice (mtproto-proxy) - Open port 443 in
ufw(if active) - Apply TCPMSS=88 iptables rule (passive DPI bypass)
- Install IPv6 hop script (optional cron auto-rotation with
CF_TOKEN+CF_ZONE) - Print a ready-to-use
tg://connection link
To enable IPv6 auto-hopping (Cloudflare DNS rotation on ban detection), you must provide Cloudflare API credentials. The script uses these to update your domain's AAAA record to a new random IPv6 address from your server's /64 pool when it detects DPI active probing.
CF_ZONE(Zone ID):- Go to your Cloudflare dashboard and select your active domain.
- On the right sidebar of the Overview page, scroll down to the "API" section and copy the Zone ID.
CF_TOKEN(API Token):- Click "Get your API token" below the Zone ID (or go to My Profile -> API Tokens).
- Click Create Token -> Create Custom Token.
- Permissions:
Zone|DNS|Edit. - Zone Resources:
Include|Specific zone|<Your Domain>. - Create the token and copy the secret string.
You can either pass variables directly inline:
curl -sSf https://raw.githubusercontent.com/sleep3r/mtproto.zig/main/deploy/install.sh | \
sudo CF_TOKEN=<your_cf_token> CF_ZONE=<your_zone_id> bashOr, for a cleaner and more secure approach, create a .env file first (you can copy .env.example as a template):
export $(cat .env | xargs)
curl -sSf https://raw.githubusercontent.com/sleep3r/mtproto.zig/main/deploy/install.sh | sudo -E bashStep-by-step instructions
1. Install Zig on the server
# x86_64
curl -sSfL https://ziglang.org/download/0.15.2/zig-linux-x86_64-0.15.2.tar.xz | \
sudo tar xJ -C /usr/local
sudo ln -sf /usr/local/zig-linux-x86_64-0.15.2/zig /usr/local/bin/zig
# Verify
zig version # → 0.15.22. Build the proxy
git clone https://github.com/sleep3r/mtproto.zig.git
cd mtproto.zig
zig build -Doptimize=ReleaseFastOr cross-compile on your Mac:
zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux
scp zig-out/bin/mtproto-proxy root@<SERVER_IP>:/opt/mtproto-proxy/3. Configure
sudo mkdir -p /opt/mtproto-proxy
sudo cp zig-out/bin/mtproto-proxy /opt/mtproto-proxy/
# Generate a random secret
SECRET=$(openssl rand -hex 16)
echo $SECRET
sudo tee /opt/mtproto-proxy/config.toml <<EOF
[server]
port = 443
# tag = "<your-promotion-tag>" # Optional: 32 hex-char promotion tag from @MTProxybot
[censorship]
tls_domain = "wb.ru"
mask = true
fast_mode = true
[access.users]
user = "$SECRET"
EOF4. Install the systemd service
sudo cp deploy/mtproto-proxy.service /etc/systemd/system/
sudo useradd --system --no-create-home --shell /usr/sbin/nologin mtproto
sudo chown -R mtproto:mtproto /opt/mtproto-proxy
sudo systemctl daemon-reload
sudo systemctl enable mtproto-proxy
sudo systemctl start mtproto-proxy5. Open port 443
sudo ufw allow 443/tcp6. Generate connection link
The proxy prints links on startup. Check them with:
journalctl -u mtproto-proxy | head -30Or build it manually:
tg://proxy?server=<SERVER_IP>&port=443&secret=ee<SECRET><HEX_DOMAIN>
Where <HEX_DOMAIN> is your tls_domain encoded as hex:
echo -n "wb.ru" | xxd -p # → 77622e7275# Status
sudo systemctl status mtproto-proxy
# Live logs
sudo journalctl -u mtproto-proxy -f
# Restart (e.g., after config change)
sudo systemctl restart mtproto-proxy
# Stop
sudo systemctl stop mtproto-proxyCreate a config.toml in the project root:
[server]
port = 443
tag = "1234567890abcdef1234567890abcdef" # Optional: promotion tag from @MTProxybot
[censorship]
tls_domain = "wb.ru"
mask = true
fast_mode = true
[access.users]
alice = "00112233445566778899aabbccddeeff"
bob = "ffeeddccbbaa99887766554433221100"Configuration reference
| Section | Key | Default | Description |
|---|---|---|---|
[server] |
port |
443 |
TCP port to listen on |
[server] |
tag |
(none) | Optional 32 hex-char promotion tag from @MTProxybot |
[censorship] |
tls_domain |
"wb.ru" |
Domain to impersonate / forward bad clients to |
[censorship] |
mask |
true |
Forward unauthenticated connections to tls_domain to defeat DPI |
[censorship] |
fast_mode |
false |
Recommended. Drastically reduces RAM/CPU usage by natively delegating S2C AES encryption to the Telegram DC |
[access.users] |
<name> |
-- | 32 hex-char secret (16 bytes) per user |
Tip Generate a random secret:
openssl rand -hex 16
Note The configuration format is compatible with the Rust-based
telemtproxy.
If your Telegram app is stuck on "Updating...", your provider or network is dropping the connection.
Often, mobile networks will connect instantly because they use IPv6, but Home Wi-Fi internet providers block the destination's IPv4 address directly at the gateway. Solution: Enable IPv6 Prefix Delegation on your home Wi-Fi router.
- Go to your router's admin panel (e.g.,
192.168.1.1). - Find the IPv6 or WAN/LAN settings.
- Enable
IPv6, and specifically check IA_PD (Prefix Delegation) for the WAN/DHCP client, and IA_NA for the LAN/DHCP Server. - Reboot the router and verify your phone gets an IPv6 address at test-ipv6.com.
If your iPhone is connected to a commercial/premium VPN and stuck on "Updating...", the VPN provider is actively dropping the MTProto TLS traffic using their own DPI. Solutions:
- Switch Protocol: Try switching the VPN protocol (e.g., Xray/VLESS to WireGuard).
- Self-Host: Use a self-hosted VPN (like AmneziaWG) on your own server.
If you run both this proxy and AmneziaVPN (or a WireGuard Docker container) on the same server, iOS clients will route proxy traffic inside the VPN tunnel, and Docker will drop the bridge packets. Solution: Allow traffic from the VPN Docker subnet:
iptables -I DOCKER-USER -s 172.29.172.0/24 -p tcp --dport 443 -j ACCEPTMIT © 2026 Aleksandr Kalashnikov