Bypassing Telegram API Blocks with Cloudflare Workers

Bypassing Telegram API Blocks with Cloudflare Workers
My Telegram bot worked fine for two months, then started timing out with 504 errors that I could not explain. The bot was running on a small VPS, the bot token was valid, and the code had not changed. The issue was upstream of me: Telegram had flagged the IP range of my VPS provider, and every request from that range was getting throttled or dropped at the edge.
I had two options. Pay for a static IP from a "clean" provider, or proxy the traffic through something that already has a clean reputation. The second option was free, took 15 minutes, and taught me how useful a tiny Cloudflare Worker can be.
Why VPS IPs Get Flagged
Telegram, like most large APIs, maintains a reputation score for every IP that hits its endpoints. If too many bots run from a single IP range and some of them are abusive, the entire range gets a worse score. Symptoms show up as 504 Gateway Timeout, intermittent hangs, or messages that send but never deliver a webhook response.
When you rent a 1GB VPS for $5 a month, you are sharing an IP range with hundreds of other tenants. Some of them run spammy bots. The shared reputation is the price of the cheap rent.
Cloudflare Workers run on Cloudflare's edge network. Those IPs have a long-standing clean reputation with every major API. By proxying my bot's traffic through a Worker, I inherit that reputation.
Step 1: Create a Cloudflare Account
The free tier is enough. Sign up at dash.cloudflare.com/sign-up with an email and password, verify the email, and you are in.
Step 2: Create a Worker
In the dashboard:
- Click Workers & Pages in the left sidebar
- Click Create, then Create Worker
- Click Deploy (the default hello world is fine for now)
- Click Edit Code (the blue button)
- Delete all the default code
- Paste in the proxy code (below)
- Click Save and Deploy (top right)
The proxy code itself is small. It accepts any incoming POST, forwards the body and headers to api.telegram.org, and streams the response back. There is no magic; it is a 30-line pass-through.
Step 3: Copy the Worker URL
After deploy, Cloudflare gives you a URL that looks like:
https://tg-proxy-<your-worker-name>.workers.dev
That is the only piece of information you need to take back to your bot.
Step 4 (Optional): Add a Shared Secret
For a small extra layer of security, set an environment variable on the Worker:
- Worker page, then Settings, then Variables
- Add a variable named
SHARED_SECRET - Set the value to a 32-character random string (generate with
openssl rand -hex 16)
With this set, your bot includes the secret as a header on every request, and the Worker rejects anything that does not match. It does not stop a determined attacker, but it stops casual probing.
If you use the secret, your VPS .env needs both values:
TELEGRAM_PROXY_URL=https://tg-proxy-<your-worker-name>.workers.dev
TELEGRAM_PROXY_SECRET=<same-secret>
Step 5: Point the Bot at the Proxy
On the VPS, update the bot's environment:
TELEGRAM_PROXY_URL=https://tg-proxy-<your-worker-name>.workers.dev
Then restart the bot service. From this point on, every Telegram API call the bot makes goes VPS to Cloudflare to Telegram, and the response comes back the same way.
What the Logs Look Like When It Works
After the switch, the bot log switches from a stream of timeouts to a clean sequence:
[INFO] Proxy configured: https://tg-proxy-<your-worker-name>.workers.dev
[INFO] [Telegram] POST /bot<token>/sendMessage → 200 OK
[INFO] Message sent
No more 504s. No more hangs. Round-trip latency from a Southeast Asia VPS to Cloudflare's edge and back to Telegram is usually under 300ms.
The Free Tier Math
Cloudflare's free Workers tier gives you 100,000 requests per day. That sounds like a lot until you do the math for an active bot.
A normal chat interaction triggers between 1 and 10 requests: a getUpdates or webhook acknowledgment, a sendMessage, maybe a sendChatAction to show the typing indicator. At 10 requests per chat, the free tier supports 10,000 chats per day. For a personal bot, that is overkill.
When I outgrow the free tier, the paid Workers plan is $5 per month for 10 million requests. That is enough for a small commercial bot. I am not there yet, and may never be.
Gotchas That Cost Me Twenty Minutes
The Worker URL must be HTTPS. Cloudflare enforces this. If you accidentally type http://, the bot will get cryptic connection errors. Always check the protocol.
CORS does not apply here. I wasted ten minutes adding CORS headers before realizing Telegram's Bot API does not enforce CORS; it is server-to-server. Skip CORS entirely.
The default Worker code logs to the Cloudflare dashboard, not to your bot log. If you want to see proxy traffic, open the Worker in the Cloudflare dashboard and watch the Logs tab. Do not expect to see it on the VPS.
The shared secret has to match exactly. A trailing newline in openssl rand -hex 16 output has bitten me more than once. Always copy the value, never paste a wrapped version.
Free tier has a 10ms CPU limit per request. The pass-through proxy finishes in under 1ms. If you start adding request inspection, validation, or transformation, watch the CPU time. The dashboard shows it under Metrics.
When This Pattern Is the Wrong Tool
If your bot is genuinely sending spam or doing something abusive, a Worker proxy will not save you. Telegram will eventually flag the Worker too, and you will be back where you started. The proxy fixes a reputation problem, not a behavior problem.
If your bot needs millisecond-level latency for trading or gaming, the extra hop through Cloudflare may be too much. Measure before and after.
For everything else (personal bots, customer support bots, internal team bots, notification bots), the Cloudflare Worker pattern is the cheapest, fastest fix for shared-IP reputation problems. It is also a useful general-purpose pattern: when an upstream API has a reputation-based block on your infrastructure, putting a clean-IP relay in front of it is often the shortest path to a working system.