How to Host 4K Video on Cloudflare R2 for Under $2 a Month
This article walks through the full process of delivering high‑quality 4K video at minimal cost by comparing storage providers, choosing Cloudflare R2 for its free outbound bandwidth, encoding with FFmpeg, packaging with HLS, uploading via rclone, and playing back with HLS.js or a custom web component.
Why a New Video Hosting Solution Was Needed
Our platform serves thousands of 4K programming tutorials, consuming over 15 TB of bandwidth each month and requiring both bright and dark versions of each video to match UI themes, which doubled storage and encoding workload. We needed a solution that was reliable, developer‑friendly, high‑performance, and cheap.
Key Requirements
Reliability – near‑100 % uptime on all browsers and devices.
Developer‑friendly – programmatic access to metadata such as duration and internal IDs.
Performance – users worldwide should receive the highest resolution their connection can support.
Affordability – low storage and egress costs for a startup.
Challenges of 4K Video Hosting
Bandwidth cost – high‑resolution streams use a lot of data.
Storage cost – large files increase disk expenses.
Compute intensive – encoding 4K video consumes significant CPU.
Scalability – infrastructure must grow with the audience.
User experience – playback must stay smooth even on slow connections.
Provider Cost Comparison
We examined several commercial video‑hosting services and extracted their monthly storage and egress pricing:
Cloudflare R2 – free outbound traffic, $0.015 / GB storage.
Vimeo – $0.10 / GB egress.
Wistia – $399 / month + $0.33 / GB egress.
Mux – $0.0036 / minute egress.
R2 is an object store only; the other services bundle encoding, players, analytics, and marketing tools that we do not need.
Understanding Cloudflare R2’s Free Egress
When R2 launched in 2022 it promised free outbound bandwidth because Cloudflare already pays a fixed cost for the data that traverses its network for security services. The free tier gives 10 GB storage and 10 million B‑class requests per month; we exceeded the storage limit, paying only $0.015 per additional GB.
Choosing R2 and Defining the Workflow
Our main remaining problem was how to encode more than 20 hours of 4K video and upload the resulting HLS assets to an R2 bucket.
Adaptive Bitrate Streaming with HLS
Instead of serving a single 4K MP4 file, we use HTTP Live Streaming (HLS) to break the video into short segments (3‑8 seconds) and provide multiple bitrate variants. The player automatically selects the best variant for the viewer’s current network speed and can switch up or down without interruption.
Benefits of HLS
Adaptive quality – reduces buffering on slow connections.
Broad compatibility – works on modern browsers and mobile devices.
Scalability – small segments are easy to cache on CDNs.
Efficient caching – CDN can store segments and lower bandwidth usage.
Encoding with FFmpeg
FFmpeg is our preferred open‑source tool for transcoding. We built a custom script that generates 720p, 1080p, and 2160p variants, sets appropriate H.264 profiles and levels, and outputs HLS‑compatible .ts segments and .m3u8 playlists.
# Define resolutions, bitrates, and output names
RESOLUTIONS=("1280x720" "1920x1080" "3840x2160")
BITRATES=("1200k" "2500k" "8000k")
OUTPUTS=("720p" "1080p" "2160p")
PLAYLISTS=()
for i in "${!RESOLUTIONS[@]}"; do
RES="${RESOLUTIONS[$i]}"
BITRATE="${BITRATES[$i]}"
OUTPUT_NAME="${OUTPUTS[$i]}"
PLAYLIST="${OUTPUT_NAME}.m3u8"
PLAYLISTS+=("$PLAYLIST")
if [ "$OUTPUT_NAME" == "2160p" ]; then
PROFILE="high"
LEVEL="5.1"
elif [ "$OUTPUT_NAME" == "1080p" ]; then
PROFILE="high"
LEVEL="4.2"
else
PROFILE="main"
LEVEL="3.1"
fi
echo "Processing $OUTPUT_NAME..."
ffmpeg -y -i "$INPUT_FILE" \
-c:v libx264 -preset veryfast -profile:v "$PROFILE" -level:v "$LEVEL" -b:v "$BITRATE" -s "$RES" \
-c:a aac -b:a 128k -ac 2 \
-g $GOP_SIZE -keyint_min $GOP_SIZE -sc_threshold 0 \
-force_key_frames "expr:gte(t,n_forced*4)" \
-hls_time 4 -hls_list_size 0 -hls_flags independent_segments \
-hls_segment_filename "$OUTPUT_DIR/${OUTPUT_NAME}_%03d.ts" \
"$OUTPUT_DIR/$PLAYLIST" || { echo "Error processing $OUTPUT_NAME"; exit 1; }
doneInspecting Source Streams with ffprobe
We used
ffprobe -v quiet -print_format json -show_streams -show_format https://stream.mux.com/F3tggXWeen7Dy00coMp1n4j3KHP6ZGhVM.m3u8to capture the exact codec, container, and bitrate settings of Vimeo and Mux streams, then tuned our own parameters to match or improve upon them.
Uploading to R2 with rclone
Because R2 implements the S3 API, any S3‑compatible tool works. We chose rclone for its simplicity:
# Copy new files to R2
rclone copy /local/path r2:bucket/bucket-name
# Sync local and remote directories (deletes removed files)
rclone sync /local/path r2:bucket/bucket-nameTesting Playback
After uploading, each HLS variant folder contains a .m3u8 playlist. Using the HLS.js demo tool we verified smooth playback across browsers. The playlist URL can be fed into any HLS‑compatible player.
Embedding HLS in a Web Page
iOS Safari supports native HLS in the <video> element; for other browsers we load HLS.js:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS Video Example</title>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
<video id="video" controls></video>
<script>
if (Hls.isSupported()) {
var video = document.getElementById('video');
var hls = new Hls();
hls.loadSource('https://your-cdn-url.com/path/to/playlist.m3u8');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() { video.play(); });
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = 'https://your-cdn-url.com/path/to/playlist.m3u8';
video.addEventListener('canplay', function() { video.play(); });
}
</script>
</body>
</html>For projects that prefer a custom element, Mux provides hls-video-element, which can be used as
<hls-video controls src="https://stream.mux.com/... .m3u8"></hls-video>and offers the same <video> API.
Conclusion
By combining HLS adaptive bitrate streaming, local FFmpeg encoding, Cloudflare R2’s free egress, and HLS.js (or a custom web component), we built a cost‑effective, fully controllable 4K video delivery pipeline that saves tens of thousands of dollars compared with traditional video‑hosting platforms while maintaining a smooth user experience.
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
