Skip to content

Instantly share code, notes, and snippets.

Last active July 18, 2024 21:29
Show Gist options
  • Save rameerez/8debfc790e965009ca2949c3b4580b91 to your computer and use it in GitHub Desktop.
Save rameerez/8debfc790e965009ca2949c3b4580b91 to your computer and use it in GitHub Desktop.
Telegram Proxy How-To: complete and up-to-date MTProxy tutorial

How to set up a Telegram Proxy (MTProxy)

This tutorial will teach you how to set up a Telegram MTProxy on an Ubuntu 22.04 sever using AWS Lightsail, although you can use any other Linux distribution and cloud provider.

Using a Telegram proxy is a safe, easy and effective way of overcoming Telegram bans. It's useful, for example, to keep using Telegram under tyrannical regimes, or to circumvent judges' decisions to block Telegram.

Telegram proxies are a built-in feature in all Telegram apps (both mobile and desktop). It allows Telegram users to connect to a proxy in just one or two clicks / taps.

Telegram proxies are safe: Telegram sends messages using their own MTProto secure protocol, and the proxy can only see encrypted traffic – there's no way for a proxy to decrypt the traffic and read the messages. The proxy does not even know which Telegram users are using the proxy, all the proxy sees is just a list of IPs.

This guide exists because the README in the official TelegramMessenger/MTProxy repo is both incomplete and hasn't been updated for the past 5 years, and fails at multiple points if you try following the steps described. This is an updated version as of March 2024. There's also an official Docker image, but it's also outdated.

I've used these exact steps to set multiple proxies myself, including my public Telegram proxy that you can use if you don't want to go through the hassle of setting up your own.


To begin, launch a clean Ubuntu 22.04 instance. I'm using AWS Lightsail. I chose a $3.5/mo instance (512MB, 2vCPUs, 1TB transfer), which should be enough for non-intensive usage. You can choose a bigger instance, or set up your server in any other cloud provider (DigitalOcean, Linode, Hetzner, etc) based on your needs and preferences. Note that the physical location you choose for the instance has to be a place where Telegram is not banned / restricted for the proxy to work.

  1. ssh into the machine you've just launched:
ssh ubuntu@ip
  1. Update apt:
sudo apt-get update
  1. Install dependencies:
sudo apt install git curl build-essential libssl-dev zlib1g-dev
  1. Clone the repo:

We're going to use an unofficial MTProxy community fork instead of the official one. Why? The official Telegram MTProxy repo is considered abandoned: it hasn't been updated for many years. Many problems that need fixing haven't been fixed, and if you try using the official repo code, MTProxy will unexpectedly break in production. This fork is a community effort to keep things up to date so you can keep running MTProxy in production without surprises.

git clone
cd MTProxy
  1. Change the Makefile and add the -fcommon flag to CFLAGS and LDFLAGS as per this PR
nano Makefile

Save and exit

  1. Build the binaries

Make sure it compiles without errors.

  1. Move the binary to /opt/MTProxy for ease of running:
sudo mkdir /opt/MTProxy
sudo cp objs/bin/mtproto-proxy /opt/MTProxy/
  1. Go to the new directory:
cd /opt/MTProxy
  1. Obtain the Telegram secret:
sudo curl -s -o proxy-secret
  1. Obtain the Telegram configuration:
sudo curl -s -o proxy-multi.conf
  1. Generate a proxy secret. This will output a string of random numbers and letters. Keep the result at hand, you will need it in a few steps:
head -c 16 /dev/urandom | xxd -ps
  1. Create a mtproxy user to run the proxy:
sudo useradd -m -s /bin/false mtproxy
  1. Update the ownership of the MTProxy directory to the new user
sudo chown -R mtproxy:mtproxy /opt/MTProxy
  1. Allow traffic on port 8443 by opening the ports in the AWS Lightsail instance:
    • Navigate to your AWS Lightsail instance
    • In the Networking tab, under "IPv4 Firewall", click "Add rule"
    • Add a rule for a "Custom" TCP protocol on 8443. Make sure "Duplicate rule for IPv6" is active
    • Click "create"
    • Since you're at it: close port 80, which is open by default, because we're not going to use it

If your instance uses the ufw firewall, you will also need to do:

sudo ufw allow 8443/tcp
  1. Now we need to know our AWS instance's private and public IP to pass them to MTProxy.

All AWS instances are behind a NAT, and this causes the RPC protocol handshake to fail if a private-to-public network address translation is not passed explicitly to MTProxy as the --nat-info param. If you don't do this, the proxy will look like it's running normally, but Telegram clients will not be able to connect, and the app will show a message like "Proxy unavailable" or an infinite "Conecting..." message.

If you don't know how to look up your AWS instance's public and private IPs, follow these steps

Once you have your private and public IP, which should look something like and, keep them at hand because you'll need them in a moment and continue.

  1. Set up a systemd service to run the proxy:
sudo nano /etc/systemd/system/MTProxy.service

Copy the folliwng config, make sure you edit it with your own params:


ExecStart=/opt/MTProxy/mtproto-proxy -u mtproxy -p 8888 -H 8443 -S <YOUR_SECRET_FROM_STEP_11> --aes-pwd proxy-secret proxy-multi.conf -M 1 --http-stats --nat-info <YOUR_PRIVATE_IP>:<YOUR_PUBLIC_IP>


Save and exit

  1. Reload the systemd daemons:
sudo systemctl daemon-reload
  1. Test the MTProxy service and verify it started just fine:
sudo systemctl restart MTProxy.service

After that check the proxy status, it should be active:

sudo systemctl status MTProxy.service

The proxy is ready!

You should now be able to connect to it inside Telegram by using a link like:


Or share it literally anywhere by using an HTTP link:<YOUR_PUBLIC_IP>&port=8443&secret=<YOUR_SECRET_FROM_STEP_11>
  1. Make sure you enable the service so the MTProxy starts even if you reboot the machine:
sudo systemctl enable MTProxy.service
  1. Set up a cron job to update proxy-multi.conf on a daily basis. Telegram recommends that proxies update their Telegram config information at least once a day, since it may change. To do that with the right permissions, we need to set up a cron job as the root user:
  • Switch to the root user:
sudo su
  • Open the root user's crontab file for editing:
crontab -e

If prompted, choose an editor (e.g., nano) to edit the crontab file.

  • In the crontab file, add the following line at the end. This will set up the update task to be run every day at 4am. Since the proxy might experience a very short downtime while restarting the MTProxy service, choose a time (usually at night) that minimizes the effects of downtime.
0 4 * * * curl -s -o /opt/MTProxy/proxy-multi.conf && chown -R mtproxy:mtproxy /opt/MTProxy && systemctl restart MTProxy.service

Save and exit the root session.

✨ Congrats!

Your proxy is now all set and will continue to be updated automatically!

You can leave it here – but there's more you can do, if you want.

For example, Telegram rewards people that set up proxies by allowing them to promote a channel of their choice to all users connected to the proxy. This channel shows up in the top of their chat list labeled as "Proxy Sponsor".

If you want to register your proxy to get usage statistics and set a promoted channel to proxy users, talk with Telegram's official MTProxybot – it will give you a tag you can pass to mtproto-proxy with the flag -P in the systemd config from step 16, like this:

ExecStart=/opt/MTProxy/mtproto-proxy -u mtproxy -p 8888 -H 8443 -S <YOUR_SECRET_FROM_STEP_11> -P <YOUR_MTPROXYBOT_TAG> --aes-pwd proxy-secret proxy-multi.conf -M 1 --http-stats --nat-info <YOUR_PRIVATE_IP>:<YOUR_PUBLIC_IP>

This way MTProxyBot can keep track of your proxy's requests to compile stats and set the promoted channel.

As a finishing touch, you can also use your own domain/subdomain instead of your public instance's IP, for more readable proxy names and URLs, like what I did with my proxy

To do this, just set up a DNS "A" record pointing to your proxy's IP. If you're using Cloudflare, make sure to turn off the proxy feature for that particular record. Oddly enough, the domain/subdomain you use can't contain hyphens; just stick to alphanumeric characters.

Thanks for reading and happy proxying!

Copy link

manueldeprada commented Mar 24, 2024

In fedora/CentOS, the dependencies are sudo dnf install git curl make automake gcc gcc-c++ kernel-devel g++ openssl-devel zlib-devel xxd

If you are using a VPS with UFW, just do sudo ufw allow 8443/tcp on step 14.

Copy link

You missed on step, enable the daemon on restart:
sudo systemctl enable MTProxy.service

In my hosting I receive the next error:
mtproto-proxy: common/pid.c:42: init_common_PID: Assertion `!(p & 0xffff0000)' failed.

This is caused because my PID's are too big. I needed to reboot my server to get smallers pids and now works. I think that if I reboot the daemon in the near future, I'll get the same error.

There's a bug open with this error: support systems with long PIDs

Copy link

You missed on step, enable the daemon on restart: sudo systemctl enable MTProxy.service

@davrodfer Thanks for the heads up! Just updated the gist with this

Copy link

If you are using a VPS with UFW, just do sudo ufw allow 8443/tcp on step 14.

@manueldeprada added to the tutorial, thanks!

Copy link

pauvt commented Mar 25, 2024

Thanks for the updated tutorial! I followed all the steps and it's working perfect using Oracle Cloud free Tier.

I only needed to do an additional step to update the iptables to add the 8443 port:

iptables -I INPUT 6 -m state --state NEW -p tcp --dport 443 -j ACCEPT
netfilter-persistent save

Copy link

rameerez commented Mar 28, 2024

@davrodfer I woke up today to find my Telegram proxy service failed restarting overnight because of the bug you mentioned (Assertion '!(p & 0xffff0000)' failed)

Using Ubuntu 22.04 LTS on an x86_64 machine.

Here are the logs after trying to start mtproto-proxy:

[69586][2024-03-28 14:35:08.197723 local] Invoking engine mtproxy-0.01 compiled at Mar 24 2024 19:19:45 by gcc 11.4.0 64-bit after commit dc0c7f3de40530053189c572936ae4fd1567269b
[69586][2024-03-28 14:35:08.197888 local] config_filename = 'proxy-multi.conf'
[69586][2024-03-28 14:35:08.198120 local] creating 8 workers
mtproto-proxy: common/pid.c:42: init_common_PID: Assertion `!(p & 0xffff0000)' failed.
[pid 69587] [time 1711636508]
------- Stack Backtrace -------
mtproto-proxy: common/pid.c:42: init_common_PID: Assertion `!(p & 0xffff0000)' failed.
mtproto-proxy: common/pid.c:42: init_common_PID: Assertion `!(p & 0xffff0000)' failed.

I'm restarting the mtproto service daily to update the proxy-multi.conf file, so it seems like the current state of the mtproto codebase leaves us in a sort of lottery: every time you restart the service, you may or may not get a big enough pid.

From a related issue (#41):

Apparently the app receives a high PID when the OS has more than two days running.

I'll have to try the suggested fix which essentially just means editing two files and recompiling; but apparently it doesn't work for some distros, and someone is already maintaining an unnofficial fork that implements many more changes.


I've re-compiled my proxy using the unnofficial fork and so far it works good. I've been checking the commits ahead of the official repo, and they seem to have addressed and solved the PID situation. I've updated the guide to use this fork instead of the official repo.

EDIT (one week later):

I've been running that unofficial fork for one week now and it's working flawlessly. The service restarts every night as it should, and it doesn't fail restarting because of big PIDs like before, even if the machine has been running for several days.

Copy link

SimJoSt commented Mar 30, 2024

It would be amazing if mtproxy would support ARM architecture. Aside from I couldn't find anything in that regard.

Copy link

alimhdv commented Apr 4, 2024

I got a question guys , how can I make a proxy with base64 secret code like this :
I tryed erlang but it gave me a base64 secret that starts with 7 that gotta be detected easily.

Copy link

rameerez commented Apr 5, 2024

@alimhdv why do you need a base64-encoded secret, and why do you use Erlang to generate it?

The secret you're referring to is already generated in step 11 of the instructions with the command head -c 16 /dev/urandom | xxd -ps

You later pass that same secret to the service config (step 16), and also put it in the URLs you generate, so that random people trying to connect in bulk to random IPs can't connect to your proxy unless they have the secret (via the URL). You publish the secret when you publish the URL, so it effectively limits connections only to people that have the URL. That is what this particular secret is for.

To make it more clear, what head -c 16 /dev/urandom | xxd -ps does is:

  • /dev/urandom contains pseudo-random data. head -c 16 takes the first 16 bytes of that pseudo-random data. This is useful, for example, to generate secret keys. Try it yourself, you'll only see random characters on screen (different random characters each time)

  • xxd is a utility that makes hex dumps. Any file you pass to it, it will give you the hexadecimal dump of it.

Try it! Do nano ./test and write only the letter a, save and exit. If you xxd ./test, it will say:

00000000: 610a                                     a.

which means: "at position 00000000, the hex contents are 610a, which correspond to a."

Since this output is a bit unreadable, you can pass the -ps option to xxd to output only the hex dump, and you'll get only 610a as output. 610a is the hex encoding of the character 61, which corresponds to lowercase a, and character 0a, which corresponds to a line feed.

So, if you pass the first 16 bytes of /dev/urandom to xxd, it just gives you the hexadecimal representation of those bytes, which in practice is just a random hex string of 32 characters, which is the secret we use.

If for whatever reason you still want a base64-encoded string as your secret, you can just pass the outputs of /dev/urandom to base64 instead of xxd, like this:

head -c 32 /dev/urandom | base64

This will give you a base64-encoded string representing the first 32 bytes of /dev/urandom, which would look something like:


Both base64-encoded and hex-encoded strings are perfectly valid random secret keys, especially for our use case. Using one encoding over the other does not mean it's more secure, they both represent the same random data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment