← back to blog

Testing an MTProto proxy before you hand it to users

An MTProto proxy that works when you test it from the same machine it’s running on tells you almost nothing. It tells you the process is alive and listening on a socket. It doesn’t tell you whether a user three networks away, behind a different ISP’s NAT and DPI, can actually get a stable connection to Telegram through it. That gap is where most “the proxy doesn’t work” reports come from, and it’s avoidable if you test the right things before you publish the tg:// link.

This isn’t a theoretical exercise. If you run proxy infrastructure and hand configs to other people, a broken proxy costs you their trust the first time it fails, and most of the time it fails silently, not with an error message.

What actually happens when a client connects

Telegram’s official clients connect to an MTProto proxy over a raw TCP connection. The client sends an initial packet that includes the secret you gave it. If the secret starts with dd, the proxy is in “random padding” mode. If it starts with ee, the proxy is running in fake-TLS mode, where the whole handshake is dressed up to look like a normal TLS ClientHello to anything watching the wire.

The proxy reads that secret, decrypts the obfuscated header, and figures out which Telegram data center to forward the connection to (this is why proxies advertise a dc_id). From that point it’s mostly relay: bytes in from the client, bytes out to the real Telegram server, and back.

Every failure mode you’ll actually see in production sits somewhere in that chain: the port isn’t reachable, the secret doesn’t match what the proxy is configured with, the DC routing is wrong, or the relay itself falls over under more than one connection. Testing means checking each of those links, not just confirming the process didn’t crash.

Check reachability from outside your own network

The single most common false pass is testing from the same LAN or the same host as the proxy. Local traffic doesn’t cross the NAT, doesn’t touch the firewall rule you meant to add, and doesn’t reveal that your router is forwarding the wrong port. A proxy that answers on localhost:443 can still be completely unreachable from the internet.

Test from somewhere genuinely outside: a VPS you control, a phone on mobile data with wifi off, or a second person on a different connection. Open the actual port with a plain TCP tool, not a browser, since Telegram’s proxy protocol isn’t HTTP and a browser will just report a generic connection failure that doesn’t tell you where it broke. If the port doesn’t open from outside but does from inside, the problem is routing or firewall, not the proxy software.

Confirm the secret and DC you’re handing out are the ones live on the server

It sounds too basic to mention, but it’s a recurring failure: the config you’re about to publish has a secret that was rotated on the server last week, or a dc_id that got changed when the server moved. Pull the live config off the proxy itself right before you publish, don’t reuse a value from a note or a previous handoff. A stale secret doesn’t produce a helpful error on the client side. It just refuses to connect, and the user has no way to know whether the problem is their network or your config.

Test with more than one client at once

A proxy that handles a single test connection fine can still fall over the moment two or three real users hit it simultaneously. Each client is a separate TCP connection that the proxy process has to hold open and relay independently. Depending on what’s running the proxy, whether it’s the reference MTProxy binary or a rewritten Go or Rust implementation, there’s a ceiling somewhere: open file descriptor limits, a worker pool that isn’t sized for concurrency, or a connection table that wasn’t built to scale past a handful of users.

Before you publish a proxy for shared use, connect from two or three different devices at the same time and check that all of them stay connected and can actually send messages, not just complete the initial handshake. A proxy that accepts the connection but stalls on real traffic under load looks identical to a working proxy in a quick single-client test.

Watch the fake-TLS handshake for leaks, not just success

If you’re running ee-prefixed secrets for the fake-TLS obfuscation, the point of that mode is to make the connection look like ordinary TLS to anyone doing deep packet inspection on the wire. That only works if the handshake is clean: the domain the proxy presents has to match what’s configured in the secret, and the response shouldn’t contain anything that gives away it isn’t real TLS.

You can check this at the packet level with a capture tool while a client connects, looking at the ClientHello and the server’s response the same way you’d audit any TLS handshake. What you’re checking for is consistency, not cleverness: the SNI in the handshake matching the domain baked into the secret, and no stray bytes or timing that would flag the connection as something other than a normal HTTPS session to whoever is watching. If those don’t line up, the fake-TLS mode isn’t buying you anything, it just looks like an oddly early or malformed TLS session, which can be more conspicuous than no obfuscation at all.

Confirm the proxy survives a restart

Servers reboot. Processes crash. If the proxy isn’t set to restart automatically, whether that’s a systemd unit, a supervisor process, or whatever init system the server uses, a routine reboot after a kernel update turns into an outage that nobody notices until users start reporting it. Test this directly: restart the server or kill the proxy process and confirm it comes back on its own, without you SSHing in to start it by hand. This is a five-minute check that catches a failure mode that otherwise shows up at the worst possible time, usually days after you’ve stopped thinking about that particular proxy.

Recheck after any IP or hosting change

If the server’s IP address changes, whether from a provider reassignment, a migration, or a NAT change upstream, any config you’ve already handed out is now pointing at a dead address. This is easy to miss because the proxy itself is running fine on the new IP, it’s just that nobody using the old config can reach it anymore. Anytime infrastructure moves, treat every existing proxy link tied to that server as untrusted until you’ve re-verified it from outside, the same way you did on first handoff.

A short pre-handoff checklist

Before you send a proxy config to anyone, run through this in order:

  1. Pull the live secret and dc_id from the server, don’t reuse a saved value.
  2. Test the port from a network you don’t control, not from the server itself.
  3. Connect two or more clients at once and confirm they can send messages, not just connect.
  4. If using fake-TLS, check the handshake for a matching SNI and no obvious tells.
  5. Restart the proxy process (or the server) and confirm it comes back without manual intervention.
  6. Note the server’s current IP so you can catch drift later if the config stops working.

None of this takes more than a few minutes once it’s routine, and it catches the failures that actually show up in the field, as opposed to the ones you’d only find by reading the source.

If you’d rather have this handled for you, along with the hosting and account-safety side of running Telegram infrastructure, take a look at what we run at telegramvault.org.

Get new guides and videos first — join the Telegram channel.

need infra for this today?