“Isn’t video against their TOS?”

For a decade, Cloudflare’s self-serve terms drew a line between HTML and everything else, and “everything else” was where your video lived. Push too many megabytes of it through the Free or Pro CDN and you got a polite email, a throttle, or a redirect. The folklore hardened into a rule everyone repeated: don’t serve video on Cloudflare unless you pay for the dedicated product or you’re on Enterprise.
In May they rewrote the rule. The old clause moved into CDN-specific terms, and the new test is where the bytes are hosted, not which plan you’re on. Video served through the CDN from a Cloudflare service (Stream, Images, or R2) is allowed on Free, Pro, and Business. Video pulled from an origin outside Cloudflare is still restricted.
So the ban didn’t die. It turned into an incentive, which points at
R2, the almost-free origin
R2 is object storage with no egress fee. Storage runs $0.015/GB per month, writes cost $4.50 a million, reads $0.36 a million, and bandwidth out is zero. On top of that the first 10 GB of storage, the first million writes, and the first ten million reads each month are free. That egress number is the point: every other object store treats bandwidth out as the meter that runs up your bill, and R2 sets it to nothing.
R2 is an origin, not a CDN. On its own it’s a bucket in a region. The arrangement that matters is R2 behind Cloudflare’s cache on a custom domain, and that arrangement is now cleared to carry video. Immutable files, meaning your HLS segments, cache at the edge and serve from there, so the reads that reach R2 are cache misses, not viewers. Storage costs pennies, egress is free, serving is allowed. For static video that ends the cost conversation.
Live streaimng on the other hand…
Ok, challenge, let’s live stream for free*
*For longer than some 30 day trial
Cloudflare handed us a free-egress origin and permission to serve video from it. What they didn’t hand us is a way to get the video in. RTMP ingest would still need the paid upgrade, and you can’t smuggle a RTMP listener into a CF Worker either: inbound to a Worker is HTTP, WebSocket, or SMTP, the socket API only dials out, and RTMP wants a connection held open for the whole broadcast. Serverless holds nothing open. So Cloudflare walks us most of the way and then waves from the shore. Thanks 😉
We take it from here with Oracle’s Always Free tier: an Arm box, 4 cores, 24 GB, 10 TB of egress a month, free for as long as the account lives. Drop nginx-rtmp on it, point the encoder at 1935, and it remuxes RTMP to HLS on disk. No transcode, so the cores stay bored. rclone pushes each segment to R2 as nginx closes it: one copy of the stream leaving the box, about 2 TB a month for a 24/7 6 Mbps channel, inside the free 10. Then R2 behind the cache serves it, and nobody ever touches your little Arm box.
The minor catch: segments cache, the live playlist doesn’t. Leave it and every viewer’s refresh hits R2; give it a 2s edge TTL and Cloudflare swallows most of the requests gracefully.
Whole thing’s up for grabs as usual: nginx config, sync script, R2 and Cloudflare setup.
Does it scale?
On the delivery side, yes, because that’s Cloudflare’s cache serving static files, which it does for a living. On the ingest side, one box is one box. Four cores will remux a surprising number of concurrent streams, but there’s no failover and the free tier gives you one region. Add viewers freely. Adding ingest redundancy means leaving the free tier.
Is it stable?
nginx-rtmp is old and boring in the way you want infrastructure to be. The soft spot is Oracle. Always Free instances get reclaimed if they idle, and free capacity in a region can dry up under you. Fine for a personal channel or a project you can babysit. Not something to hang a paid SLA on without a paid instance underneath.
How free* is it?
Ingest is free, and the Oracle box comes with a public IPv4 at no charge. A rolling live window is a couple GB, so it sits inside R2’s free 10 and storage costs nothing; the reads come off the cache, also nothing. Egress is zero on both legs that matter. The only nudge above zero is a genuinely around-the-clock channel, whose segment and playlist writes creep past R2’s free million to about a dollar a month. Stream part-time and even that disappears. The one line item that can balloon is compute: the moment you ask that box to transcode an ABR ladder instead of passing a single rendition through, you’re CPU-bound and the free cores run out. Keep it remux and the number stays near zero.
Is it worth it?
For one stream or a few, for a hobby, a community, a project that can shrug off the odd hiccup, it’s hard to beat free. For a business that needs redundancy, more than one region, and someone to page at 3am, the single box is the exact corner you can’t cut, and you may want to pay to uncut it.
Why not a media-dedicated origin?
I’ve been asked repeatedly why not/no longer using the likes of AWS/Elemental MediaStore. Awkward answer: it’s pointless since late 2020. Before that, S3 was eventually consistent, lately it’s read-after-write consistent (all we really need) and still cheaper.
R2 was strongly consistent from launch, it never had the eventual-consistency era S3 spent a decade in. Read-after-write, deletes, and list operations are all globally strongly consistent: write an object and every reader anywhere immediately sees the latest version; delete it and reads immediately return not-found; a list reflects the bucket at that point in time.