Skip to content

Surviving Active Probing

Alexey Dolotov edited this page Apr 24, 2026 · 1 revision

Surviving Active Probing: Deployment Guide

mtg with FakeTLS (ee-secrets) does a solid job hiding MTProto traffic from passive DPI. But modern censorship systems also perform active probing: they connect to suspected proxies themselves and check if the server behaves like the website it claims to be.

This page explains why proxies get detected and how to deploy mtg so that it survives both passive and active analysis.


How mtg already handles probes

A common misconception (see #458): mtg "just closes the connection" on a failed handshake. That is not true. When mtg receives a TLS ClientHello that does not pass MTProto authentication, it performs domain fronting: it rewinds the bytes, opens a TCP connection to the real host from the secret (or domain-fronting.ip), and transparently relays traffic. The probe therefore sees the real site's TLS handshake, certificate, and HTTP responses.

Why proxies still get blocked

1. SNI/IP mismatch (the #1 killer)

The secret encodes a hostname — say www.vk.com. mtg announces this in the TLS ServerName, and domain fronting connects to www.vk.com:443. But the server itself sits at some Hetzner/OVH/DigitalOcean IP that has nothing to do with VK.

Censors don't even need to probe: one DNS lookup for www.vk.com shows that the IP doesn't match, and they block it.

Fix: Use a domain you own, with its DNS A/AAAA records pointing to the proxy server's IP. Then the match is natural.

mtg doctor checks this at validation time. Starting with the upcoming release, mtg run also logs a warning at startup if a mismatch is detected.

2. No real TLS service on the same IP

Even if the domain matches, an active probe that connects with a different SNI (or no SNI) should see something normal. If there is no real TLS server behind mtg, the fronting connection fails, and the probe sees a RST or timeout — suspicious for a host that claims to serve HTTPS.

Fix: Run a real web server (Caddy, nginx) on the same machine and put an SNI router (HAProxy, sslh, nginx stream) in front of everything on port 443. The router peeks at the ClientHello SNI:

  • Your domain → mtg (FakeTLS)
  • Anything else → real web server

A ready-made docker-compose setup is proposed in contrib/sni-router/ (see the open PR).

3. TCP fingerprint of the proxy host

When mtg relays a ClientHello to the fronting domain, TLS is end-to-end between the probe and the real site. But the TCP layer (SYN, MSS, window scale, timestamps) comes from the proxy host's kernel, not from the fronting domain. A sophisticated censor can notice that the TCP stack doesn't match the expected OS of, say, vk.com's CDN.

This cannot be fixed in userspace. Mitigations:

# Normalize some TCP parameters (Linux)
sysctl -w net.ipv4.tcp_timestamps=1
sysctl -w net.ipv4.tcp_window_scaling=1
sysctl -w net.ipv4.tcp_sack=1

# Match a common MSS for your uplink MTU (usually 1460 for Ethernet)
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN \
    -j TCPMSS --set-mss 1460

These don't make the fingerprint invisible but reduce the delta between the proxy host and a typical web server.

4. Traffic timing and volume patterns

MTProto sessions are long-lived, low-bandwidth, and bursty — very different from normal HTTPS. There is no easy fix, but using the proxy through a SOCKS5 upstream (e.g. sing-box, xray) adds a hop that changes timing characteristics on the outbound side.


Recommended deployment patterns

A. Own domain + SNI router (best)

  1. Register a cheap domain (or use a subdomain you already own).
  2. Point its DNS to your VPS.
  3. Deploy mtg + HAProxy + Caddy (see contrib/sni-router/ in the open PR).
  4. Generate the mtg secret with your domain: mtg generate-secret --hex your.domain
  5. Caddy obtains a Let's Encrypt cert automatically.

Result: browsers see a real website, Telegram clients get MTProto, DPI sees consistent SNI/IP/cert.

B. CDN (Cloudflare) in front

If the domain is behind Cloudflare (or another CDN that supports TCP passthrough / Spectrum):

  1. Cloudflare terminates the first TLS layer to the browser/probe.
  2. For MTProto traffic, configure Cloudflare Spectrum or a CNAME to forward port 443 TCP to your origin.
  3. mtg on the origin sees the FakeTLS ClientHello as usual.

This hides the origin IP entirely and leverages Cloudflare's reputation.

Caveat: Cloudflare Spectrum is a paid feature on most plans.

C. Co-located with an existing web app

If you already run a web service on the VPS:

  1. Move the web service to an internal port (e.g. 8443).
  2. Add HAProxy or nginx stream on port 443 with SNI routing.
  3. Route your-domain to mtg, everything else to the web service.

No extra domain or certificate needed — just rearrange the ports.


Validating your setup

# 1. Check SNI/IP match, DC connectivity, fronting reachability:
mtg doctor /path/to/config.toml

# 2. Simulate a probe — connect with a random SNI:
openssl s_client -connect YOUR_IP:443 -servername random.example.org

# You should see the real web server's certificate, not a connection reset.

# 3. Simulate a probe — connect with the correct SNI but no MTProto:
openssl s_client -connect YOUR_IP:443 -servername YOUR_DOMAIN

# Should see the same valid certificate (domain fronting kicks in).

# 4. Check from outside (optional):
curl -v https://YOUR_DOMAIN/
# Should return your web content with a valid cert.

What this does NOT protect against

Threat Why Possible mitigation
IP/ASN reputation block The entire hosting provider is flagged Move to a less-known ASN or use a CDN
Telegram client bugs Broken ClientHello in old clients Update to Telegram Desktop ≥ 6.7.2, Android ≥ 12.6.4 (tdesktop#30513)
Traffic analysis (flow correlation) Long-lived encrypted streams with MTProto timing No easy fix; SOCKS5 upstream adds indirection
Kernel TCP fingerprint OS-level differences in SYN/ACK Only partially mitigable (see above)