← back to blog

Serving media from your own CDN instead of Telegram

Where Telegram actually stores your media

When you upload a photo, video, or document to Telegram, it doesn’t sit on a server you control. It gets pushed into Telegram’s own storage layer, distributed across their data centers, and referenced afterward by a file_id. That file_id is not a permanent, portable link. It’s a pointer that’s only meaningful in the context of your bot or account, and it can stop resolving if Telegram invalidates it, if the source message is deleted, or if the file simply ages out of whatever caching tier it landed in.

For a person swapping photos with friends, none of this matters. For anyone running a channel, a bot, or any kind of content operation on top of Telegram, it matters a lot. You don’t own the storage, you don’t control the caching behavior, and you don’t get a stable URL you can point other things at (a website, an email, a landing page) without going through Telegram’s own preview or API layer.

Telegram does have a CDN component, cdn.telegram.org, which caches certain public content (channel media, some bot-served files) at edge locations so repeated requests don’t all hit core servers. But that caching exists for Telegram’s benefit, to keep their own infrastructure fast, not as a service you can configure, purge, or rely on for your own delivery guarantees. You don’t get to set cache headers, you don’t get analytics on who’s pulling the file, and you don’t get a contract on retention.

Why this becomes a problem at scale

The Bot API caps file downloads at 20 MB unless you run your own local Bot API server, which raises the ceiling to 2000 MB but still routes everything through Telegram’s MTProto backend to fetch and re-serve the bytes. Every getFile call your bot makes counts against the same rate limits as any other API call. If your bot is repeatedly fetching the same media, say serving the same promotional image or video to hundreds of users a day, you’re making the same round trip to Telegram’s servers over and over for content that never changes.

That has two costs. The obvious one is latency and reliability: you’re dependent on Telegram’s API responsiveness for something that could just be a static file. The less obvious one is account risk. Automated systems that hammer the same endpoints with repetitive, high-frequency calls look like bot behavior, because they are bot behavior, and Telegram’s abuse detection doesn’t distinguish “legitimate media server” from “spam pattern” just because your intent is benign. If you’re running proxy infrastructure or multiple accounts already, you understand this: anything that adds unnecessary call volume through the API surface adds unnecessary exposure.

There’s also a durability problem. If a channel gets removed, if an account gets restricted, or if a chat gets banned, the media inside it can become unreachable even if you technically still have the file_id on record. You never had a real copy. You had a reference to Telegram’s copy.

What “your own CDN” actually means here

This isn’t complicated infrastructure. It means your canonical media, the actual bytes, lives in object storage you control (S3, Backblaze B2, Cloudflare R2, or similar), fronted by a CDN (Cloudflare, Bunny, Fastly, or your storage provider’s built-in edge). You get a stable URL like cdn.yourdomain.com/media/file.jpg that resolves the same way today, next month, and next year, regardless of what happens inside your Telegram account or channel.

The practical workflow looks like this: media gets uploaded once to your storage bucket, the CDN caches it at the edge according to rules you set, and any place that needs to reference the file, a bot reply, a channel post, a landing page, a webapp, points at that URL instead of re-fetching through Telegram. If you’re posting in a channel, you can still attach the media natively (Telegram will store its own copy the moment you upload it there too), but for anything programmatic, anything served repeatedly, anything you need to guarantee will still exist in six months, the CDN copy is the one that matters.

Telegram won’t proxy your CDN content for you inside a chat message, so the media still needs to actually land in Telegram if you want it to render inline. What changes is where the source of truth lives. Your bot doesn’t need to call getFile and shuttle bytes through Telegram’s backend every time someone requests the same asset. It serves the CDN URL directly, or if it needs to deliver the file into a chat, it uploads from your own storage rather than round-tripping through a previous Telegram-stored copy.

Where this intersects with account safety

If you’re running Telegram accounts through proxy infrastructure, which is most of what people in this space are doing, minimizing unnecessary API surface area is part of keeping accounts healthy. Every extra call is another data point in whatever pattern-matching Telegram’s abuse systems are running. Repetitive getFile requests for static content are exactly the kind of mechanical, non-human pattern that gets flagged alongside actual spam behavior, even though your reason for making the call is completely legitimate.

Moving static media off the API and onto your own CDN cuts that call volume down to just what’s needed to actually deliver a message. It also means that if an account does get restricted or banned, which happens, you haven’t lost your media archive along with it. The bytes are sitting in a bucket you own, addressable by a URL that has nothing to do with any Telegram account’s status.

This is a smaller version of the same principle behind running your own MTProto proxy instead of leaning entirely on Telegram’s default connection paths: control over the path between you and your content reduces the number of things that can go wrong outside your control, and reduces the number of ways an account’s behavior gets judged by systems you can’t see into.

Practical setup notes

A few things that matter in practice, not as a checklist to follow blindly but as the actual mechanics involved:

Use signed URLs if the media isn’t meant to be public. A CDN in front of private object storage can issue time-limited signed links so you’re not accidentally exposing an open bucket to anyone who guesses a filename pattern.

Set cache headers deliberately. Static media (a logo, a fixed promotional asset) can be cached for a long time. Content that changes, or that you might need to pull down for compliance reasons, needs shorter TTLs or cache-busting query parameters so a purge actually takes effect.

For video, plain file delivery works for short clips, but anything longer benefits from proper adaptive streaming (HLS or DASH) rather than shipping one large file and hoping the client’s connection holds up. That’s a separate decision from where the file lives, but it only becomes possible once you’re serving from infrastructure you control instead of a file Telegram is holding for you.

Keep a real inventory. Object storage with a CDN in front of it is still storage you’re paying for and responsible for, unlike Telegram’s, which is free but conditional on your account and channel staying alive. Know what’s in it and why.

None of this replaces Telegram as the delivery surface where your audience actually sees the content. It just means the content itself doesn’t live and die by Telegram’s rules, and your bot isn’t generating API traffic that makes it look like something it isn’t.

If you’re setting up hosting or proxy infrastructure for Telegram and want this handled as part of the setup rather than retrofitted later, that’s exactly what we do. Get in touch.

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

need infra for this today?