A Telegram bot is an HTTPS client. It calls api.telegram.org to fetch updates and to send messages, and if the network it runs on cannot reach that host, the bot is dead on arrival. A proxy fixes that, and it also gives the bot one fixed address to leave from.
This guide sets up a Telegram bot proxy in python-telegram-bot, aiogram and Telegraf, explains why a proxy does nothing for webhooks, and covers the errors people hit, including one that looks like a proxy problem and never is.
What this guide covers, and what it does not
Bots made with BotFather, logging in with their own token: a channel that posts your alerts, a support bot, a bot your team uses for deploys. Telegram’s bot developer terms still apply through a proxy, including the rule that a bot must not spam users with unsolicited messages.
Not covered here: automating personal Telegram accounts, running many accounts, or switching IPs to get around a ban or a limit. This guide is about one bot made with BotFather, and flood limits are tied to your bot token anyway, so a new IP does not reset them. Spam, mass messaging and harassment are not allowed on our network either; the acceptable use policy has the full list.
Why run a Telegram bot through a proxy?
Because the network in the way is not yours to fix. The usual reasons:
- The host cannot reach the Bot API. Some company, school and hosting networks block
api.telegram.org, and the bot times out on its first call. A proxy gets it through, where you are permitted to use it. - You need a fixed egress IP. The bot calls your own internal API, or a firewall only lets traffic out through a known proxy.
- You run the bot at home and would rather not have it leave from your home IP.
Long polling or webhooks: what a proxy can and cannot do
Telegram gives a bot two mutually exclusive ways to receive updates. Which one you use decides whether a proxy helps at all.
Long polling (getUpdates): the bot opens a request to Telegram and Telegram holds it open until an update arrives or the timeout passes. Every byte is outbound, from the bot to Telegram, so a proxy covers all of it.
Webhooks (setWebhook): Telegram calls your server. That is inbound traffic, and an outbound proxy has nothing to do with it. For a webhook you need a public HTTPS endpoint on port 443, 80, 88 or 8443. If you firewall the endpoint, allow 149.154.160.0/20 and 91.108.4.0/22, the ranges Telegram’s webhook guide lists. The replies your bot sends back still go out through the proxy, if you configure one.
So if your host cannot reach Telegram, use long polling through the proxy. If your host cannot be reached by Telegram, a proxy is the wrong tool; you need a public endpoint or a tunnel.
python-telegram-bot proxy: two settings, not one
In python-telegram-bot 20.7 and later, including the current 22.x, the builder has .proxy() for ordinary calls and .get_updates_proxy() for the long-polling request. They are separate because the library uses a separate HTTP client for getUpdates. Set only the first and the bot can send messages but never hears anything. The old proxy_url methods were removed in 22.0.
pip install -U python-telegram-botimport os
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
PROXY = "http://USER:PASS@IP:PORT"
async def ping(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("pong")
app = (
ApplicationBuilder()
.token(os.environ["TELEGRAM_TOKEN"])
.proxy(PROXY)
.get_updates_proxy(PROXY)
.build()
)
app.add_handler(CommandHandler("ping", ping))
app.run_polling()For SOCKS5, install the extra and change the scheme, using the SOCKS host, port and credentials your dashboard lists for the order:
pip install -U "python-telegram-bot[socks]"PROXY = "socks5h://USER:PASS@IP:PORT"The library uses httpx underneath, so the httpx setup page applies. One catch from the library docs: an HTTP proxy can come from the HTTPS_PROXY environment variable, but a SOCKS5 proxy cannot.
aiogram proxy setup (aiogram 3)
aiogram 3 takes the proxy on its AiohttpSession, which you hand to the Bot. It builds an aiohttp-socks connector for every proxy, HTTP included, so install the proxy extra even if you never touch SOCKS:
pip install -U "aiogram[proxy]"import asyncio
import os
from aiogram import Bot, Dispatcher
from aiogram.client.session.aiohttp import AiohttpSession
from aiogram.filters import Command
from aiogram.types import Message
dp = Dispatcher()
@dp.message(Command("ping"))
async def ping(message: Message) -> None:
await message.answer("pong")
async def main() -> None:
session = AiohttpSession(proxy="http://USER:PASS@IP:PORT")
bot = Bot(token=os.environ["TELEGRAM_TOKEN"], session=session)
await dp.start_polling(bot)
asyncio.run(main())For SOCKS5 write socks5://USER:PASS@IP:PORT. aiogram’s URL parser accepts http, socks4 and socks5 but not socks5h; it already asks the proxy to resolve hostnames, so you lose nothing. The one session carries both polling and sending.
Telegraf proxy setup (Node.js)
Telegraf 4 takes a Node http.Agent in its telegram.agent option and uses it for every Bot API call. The proxy agent packages supply one:
npm i telegraf https-proxy-agent// bot.mjs, run with: node bot.mjs
import { Telegraf } from "telegraf";
import { HttpsProxyAgent } from "https-proxy-agent";
const agent = new HttpsProxyAgent("http://USER:PASS@IP:PORT");
const bot = new Telegraf(process.env.TELEGRAM_TOKEN, { telegram: { agent } });
bot.command("ping", (ctx) => ctx.reply("pong"));
bot.launch();
process.once("SIGINT", () => bot.stop("SIGINT"));
process.once("SIGTERM", () => bot.stop("SIGTERM"));For SOCKS5, swap in socks-proxy-agent and a socks5h:// URL; the shape is the same. The Node.js proxy setup page covers both packages and passwords with special characters.
Keep the proxy out of your code
Hard-coding the proxy URL means your laptop, which can reach Telegram fine, also goes through it, and the password ends up in your repository. Read it from the environment instead, and skip the proxy when the variable is empty:
import os
from telegram.ext import ApplicationBuilder
builder = ApplicationBuilder().token(os.environ["TELEGRAM_TOKEN"])
proxy = os.environ.get("BOT_PROXY")
if proxy:
builder = builder.proxy(proxy).get_updates_proxy(proxy)
app = builder.build()The same pattern works in aiogram (pass None to the session when the variable is empty) and in Telegraf (only build the agent when it is set). Put BOT_PROXY in the service file or container environment on the one server that needs it.
Which proxy suits a Telegram bot?
A static one. A polling bot talks to one host all day, a firewall rule needs one address, and the traffic is small and constant, which is the worst possible shape for a per-gigabyte plan. A rotating residential IP buys you nothing here: Telegram does not care whether your bot looks like a home connection.
A dedicated datacenter IP is $3.20 for 30 days, with no traffic meter to watch. ISP addresses are the other static line, if a firewall or a partner insists on a consumer network. The datacenter proxies page has the details.
Is an MTProto proxy the same thing?
No. MTProto proxies are Telegram’s own proxy type for the Telegram apps: a server, a port and a secret that you paste into the app so a person can chat where Telegram is blocked. They speak Telegram’s MTProto protocol, which is what user clients use. A Bot API bot never speaks MTProto; it makes HTTPS requests to api.telegram.org, so it needs an ordinary HTTP or SOCKS5 proxy. Ours are HTTP, HTTPS and SOCKS5, not MTProto, and an MTProto proxy link will not work in any of the libraries above.
Telegram bot proxy errors, and the one that is not a proxy error
| What you see | Likely cause | What to do |
|---|---|---|
| 407 Proxy Authentication Required | Wrong username or password, or your server’s IP is not on the allowlist | Recheck the dashboard; URL-encode special characters in the password |
| Timeouts every few minutes while idle | Something on the path closes a long-poll request that sits quiet | Lower the poll timeout: timeout= in run_polling, polling_timeout= in aiogram’s start_polling |
| SSL: WRONG_VERSION_NUMBER | The proxy URL starts with https:// but the proxy speaks plain HTTP | Write http:// for the proxy, even though Telegram is https:// |
| Network is unreachable | Usually a request that went direct instead of through the proxy | In python-telegram-bot, check you set get_updates_proxy too; elsewhere, check the proxy is set where the bot is built |
| 409 Conflict: terminated by other getUpdates request | Two copies of the bot are polling with the same token | Stop the other copy. Changing the proxy changes nothing |
| 409 Conflict while a webhook is active | getUpdates does not work while a webhook is set | Call deleteWebhook, or stay on webhooks |
The idle timeout row needs a word. A long poll is a request that sits silent until something happens, and firewalls, NAT gateways and proxies all eventually close connections that look dead. If the poll timeout is longer than the shortest idle limit on the path, quiet bots see a timeout, reconnect, and carry on, which looks like a flaky proxy but is only a mismatch. A poll timeout of 30 seconds or less avoids most of it.
The 409 deserves its own line because it is the most common false alarm. People add a proxy, start the bot, see a conflict and assume the proxy broke it. What actually happened: the old copy of the bot, on a server, in a container or in another terminal, is still polling. Telegram allows one getUpdates consumer per token. The proxy was framed.
For a quick test outside the bot, this shows whether the proxy reaches Telegram and whether the token is good, in one line:
curl -x http://USER:PASS@IP:PORT "https://api.telegram.org/botTOKEN/getMe"JSON with "ok":true means both are fine. A 401 from Telegram is the token; a 407 is the proxy. For the rest, the 407 guide and the timeout guide go deeper.
Running a Discord bot too?
The Discord bot proxy guide covers discord.py and discord.js, including the gateway websocket that discord.js cannot send through a proxy, and why a shared host’s IP can get your bot blocked for someone else’s mistakes. If you are choosing between SOCKS5 and HTTP for either bot, the SOCKS5 vs HTTP guide has the short answer: HTTP, unless you have a reason.
Top-ups start at $5.
One shared datacenter IP for 30 days is $2.10. A single gigabyte of residential is $5.50. The balance never expires.