Short answer: a server in Iran can't reach api.telegram.org directly, so the bot has to send its requests through a proxy. The simplest stable setup: an SSH tunnel to a small server abroad, kept alive by autossh; the bot uses long polling through that tunnel; and both run under systemd so they come back on their own when they fail. A webhook only helps if Telegram can reach your domain from outside.

Why a server in Iran at all?
If your users are in Iran, you use an Iranian payment gateway, or your database and site already live there, a local server is cheaper and closer. There is only one problem: talking to Telegram itself. So the whole idea is to route only Telegram traffic abroad and keep everything else on this server.
Step 1: build a stable tunnel
A small VPS abroad is enough (the cheapest plan works). On the Iranian server:
sudo apt install autossh
ssh-keygen -t ed25519 -f ~/.ssh/tunnel -N ""
ssh-copy-id -i ~/.ssh/tunnel.pub user@FOREIGN_IP
Now define the tunnel as a service. File /etc/systemd/system/tg-tunnel.service:
[Unit]
Description=SOCKS tunnel for Telegram
After=network-online.target
[Service]
User=botuser
Environment=AUTOSSH_GATETIME=0
ExecStart=/usr/bin/autossh -M 0 -N -D 127.0.0.1:1080 \
-o ServerAliveInterval=20 -o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes -i /home/botuser/.ssh/tunnel user@FOREIGN_IP
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now tg-tunnel
curl -s --socks5-hostname 127.0.0.1:1080 https://api.telegram.org | head -c 100
If the last command answers, the tunnel works. ServerAliveInterval detects a dead connection quickly, and Restart=always reconnects it when it drops.
Bind port
1080to127.0.0.1only, never0.0.0.0, or anyone can use your proxy.
Step 2: connect the bot to the proxy (aiogram 3)
pip install aiogram aiohttp-socks
import asyncio, os
from aiogram import Bot, Dispatcher
from aiogram.client.session.aiohttp import AiohttpSession
session = AiohttpSession(proxy="socks5://127.0.0.1:1080")
bot = Bot(os.environ["BOT_TOKEN"], session=session)
dp = Dispatcher()
# your handlers here
async def main():
await bot.delete_webhook(drop_pending_updates=False)
await dp.start_polling(bot, polling_timeout=30)
asyncio.run(main())
Two things matter:
- Only the bot goes through the proxy. Other requests (payment gateway, local APIs) go direct, which is exactly what you want.
- Call
delete_webhookat startup. If a webhook was set earlier, polling receives nothing and you won't know why for a long time.
Step 3: run the bot as a service too
File /etc/systemd/system/mybot.service:
[Unit]
Description=Telegram bot
After=tg-tunnel.service
Requires=tg-tunnel.service
[Service]
User=botuser
WorkingDirectory=/home/botuser/bot
EnvironmentFile=/home/botuser/bot/.env
ExecStart=/home/botuser/bot/.venv/bin/python main.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Requires means the bot won't start without the tunnel, and restarts with it. Logs are always at hand:
journalctl -u mybot -f
What about webhooks?
A webhook means Telegram sends requests to your server. On an Iranian server that has two problems:
- Telegram must reach your domain from outside, which is not always reliable, especially during disruptions.
setWebhookitself and replying to users still need the proxy.
So for a server in Iran, polling through the tunnel is simpler and more stable. A webhook is worth it when traffic is high and your domain is reachable from outside (for example behind Cloudflare). Then always set a secret_token so nobody can send fake requests to your webhook URL.
When it doesn't work
| Symptom | Likely cause |
|---|---|
Cannot connect to host api.telegram.org |
The tunnel is down: systemctl status tg-tunnel |
| Bot is running but receives nothing | An old webhook is still active; call delete_webhook |
TelegramConflictError |
Another copy of the bot is polling somewhere else |
| Drops every few hours | No ServerAliveInterval, or the foreign server is unstable |
Checklist before you relax
- The tunnel is active (
systemctl status tg-tunnel) and comes back after a server reboot. - The proxy listens only on
127.0.0.1, not on every IP. - The bot depends on the tunnel via
Requiresand runs withRestart=always. - The bot token lives in
.env, not in the code or in git. - Restart the tunnel on purpose once and check the bot answers again without you.
If all five are ticked, your bot runs on the Iranian server with almost no babysitting.
FAQ
Can I use V2Ray or a ready-made proxy instead of an SSH tunnel?
Yes. Anything that exposes a SOCKS5 or HTTP proxy on 127.0.0.1 works with the same code; just change the proxy address. I suggest the SSH tunnel because it needs no extra tools and is easy to run as a service.
What if the internet goes national-only?
The Iranian server loses its link abroad and this setup stops too. Only bots running on a foreign server keep working.
I use python-telegram-bot, not aiogram.
It supports proxies too (install python-telegram-bot[socks]). When building the Application, set both .proxy(...) and .get_updates_proxy(...); forget the second and the bot can send but never receives.


