A sane AWS video streaming architecture for 2026
Upload, transcode, package, deliver, authorise, observe. A practical HLS pipeline with S3, MediaConvert, CloudFront, signed access, and the cost traps.
Zynitex Engineering
Product & engineering notes

The short answer
For most subscription video products, the sane AWS shape is: upload to a private S3 bucket, transcode with MediaConvert into HLS renditions, serve through CloudFront with Origin Access Control, and authorise playback with short-lived signed URLs or cookies. Keep the bucket private, use 4–6 second segments for VOD, and watch egress cost more closely than compute.
What this note covers
- Video is a delivery product; the CDN and the bitrate ladder matter more than the upload form.
- Use HLS with adaptive bitrate, not one large MP4.
- Never make the S3 bucket public; use OAC and signed access.
- Entitlement checks belong in your backend, not the player.
- Egress, not transcoding, is usually the bill that grows.
On this page(11 sections)
- 01The job to be done
- 02The five-piece pipeline
- 03Why HLS, not a giant MP4
- 04A bitrate ladder that doesn’t waste money
- 05Keep the bucket private
- 06Authorisation belongs in your backend
- 07The cost that surprises people
- 08Monitor the pipeline, not just the player
- 09Operations checklist
- 10Where teams get it wrong
- 11Keep it boring
Video architecture attracts over-engineering. Most subscription catalogues don’t need a multi-region DRM platform on day one. They need a private bucket, a reliable transcoder, a CDN, and playback that starts quickly on a ₹12,000 phone.
We’ve run this shape for Raykulam and Shaligram Healing: S3 + MediaConvert + CloudFront, with EC2 auto scaling behind the application and CloudWatch around it. It’s not exotic. It’s boring in the way infrastructure should be.
The job to be done
Strip away the vendor names and a video platform has five jobs: accept an upload, process it into playable formats, store the output, deliver it close to the viewer, and decide who is allowed to watch. Everything else — recommendations, analytics, downloads — sits on top of those five.
S3 source
Private bucket
MediaConvert
HLS renditions
CloudFront
Edge cache + OAC
Player
Signed access
The five-piece pipeline
| Stage | AWS piece | What it does | Failure mode |
|---|---|---|---|
| Upload | S3 with presigned multipart upload | Accepts large files without routing them through your app | Uploads through the web server; timeouts and memory spikes |
| Process | MediaConvert job | Creates HLS renditions, thumbnails, and metadata | Stuck jobs, no retry, no visible status |
| Store | Private S3 buckets | Holds masters and packaged output | A public bucket or an accidental ACL |
| Deliver | CloudFront + OAC | Caches segments at the edge and terminates TLS | Direct origin access or poor cache keys |
| Authorise | Backend + signed URL/cookie | Checks entitlement and grants short-lived access | Secrets in the browser or permanent public links |
| Observe | EventBridge, CloudWatch, alarms | Job status, errors, delivery health, cost | Silent failures discovered by customers |
Why HLS, not a giant MP4
HLS splits video into short segments described by a manifest. The player picks a rendition that fits the current bandwidth. That means faster start, fewer stalls, and better mobile behaviour. It also lets the CDN cache segments instead of streaming a unique file per user.
| Format | Use it for | Trade-off |
|---|---|---|
| MP4 | Short downloads and simple demos | Slow start, no adaptive bitrate, range-request dependency |
| HLS | VOD and live streaming | Manifest plus segments; player support varies |
| DASH | Android and desktop ecosystems | Another packaging path and player stack |
| CMAF | Low-latency HLS/DASH | More setup and fewer platform guarantees |
For VOD, 4–6 second segments are a good default. Shorter segments improve latency but increase request overhead. For low-latency live, use a different design; don’t copy a VOD ladder into a live event and hope.
A bitrate ladder that doesn’t waste money
Don’t create twelve renditions because a guide says so. Start with four or five that cover real devices, then use quality-defined variable bitrate (QVBR) or per-title analysis where your pipeline supports it.
| Rendition | Resolution | Target bitrate |
|---|---|---|
| 360p | 640 × 360 | 700–900 kbps |
| 480p | 854 × 480 | 1.2–1.5 Mbps |
| 720p | 1280 × 720 | 2.5–3 Mbps |
| 1080p | 1920 × 1080 | 4.5–5.5 Mbps |
Audio at 96–128 kbps AAC is usually enough for speech. Captions are not optional if you care about accessibility, search, and corporate buyers. Animation and film grain need more bits than an interview.
Note
The ladder is a hypothesis
Measure your own content. A 1080p talking-head video and a 1080p action sequence should not have the same bitrate budget.
Keep the bucket private
The source bucket should not be public. CloudFront should read it through Origin Access Control, and direct S3 URLs should be blocked. If a video URL works without a signature, assume it will end up in a WhatsApp group.
- Block public access at the account and bucket level.
- Use OAC, not a public bucket policy or a legacy origin access identity.
- Allow TLS only.
- Sign URLs or cookies with short expiry.
- Rotate CloudFront key pairs and store the private key on the server.
- Add WAF rules and rate limits for abuse-prone endpoints.
- Separate development, staging, and production buckets.
Authorisation belongs in your backend
The player shouldn’t decide who paid. Your API checks the subscription or purchase, then returns a short-lived signed URL or sets a signed cookie. Keep the CloudFront private key on the server.
For HLS, a signed cookie is often more practical than signing every segment URL, because the player requests a manifest and then dozens of parts. A wildcard signed URL policy can work too. Either way, the expiry should be shorter than a typical viewing session and the policy should cover only the content the viewer is entitled to.
// Server-side only. Never ship the CloudFront private key to the browser.
export async function getPlaybackUrl(userId: string, videoId: string) {
const entitled = await hasActiveAccess(userId, videoId);
if (!entitled) throw new Error("Not entitled");
return signCloudFrontUrl({
resource: `https://cdn.example.com/videos/${videoId}/*`,
expires: Date.now() + 2 * 60 * 60 * 1000,
});
}The cost that surprises people
Egress usually dominates. A simple estimate: hours viewed × average bitrate ÷ 8 = gigabytes delivered. 1,000 hours at 2 Mbps is about 900 GB, before protocol overhead. Transcoding is spiky; delivery grows with success.
| Cost driver | Lever |
|---|---|
| Delivery | Right-sized ladder, cache hit ratio, regional edge, shorter content |
| Storage | Lifecycle old masters, delete intermediates, tier colder objects |
| Transcoding | QVBR, avoid unnecessary 4K, batch jobs |
| Requests | Segment length, cache keys, ignore irrelevant query strings |
| Support | Alarms, retries, dashboards, a cost owner |
Put a CloudFront usage alarm and an AWS Budget alert on before launch. The first viral video shouldn’t be the first time you look at the bill.
Practical tip
Make cost a product metric
Track delivery GB per paying user and per active hour. If the number drifts, the ladder, cache, or content length changed.
Monitor the pipeline, not just the player
- MediaConvert job events through EventBridge, with retries and a dead-letter path.
- Duration and rendition checks after every transcode.
- CloudFront 4xx/5xx rates and cache hit ratio.
- Time to first frame and playback start failures by device.
- Upload failures and abandoned multipart uploads.
- Cost anomalies by distribution and content type.
Operations checklist
- Block public access and verify with an unauthenticated request.
- Confirm CloudFront can read the origin only through OAC.
- Set lifecycle rules for masters, intermediates, and old outputs.
- Add retries and alarms around MediaConvert jobs.
- Test signed access with an expired URL.
- Test playback on iOS Safari, Android Chrome, and a slow connection.
- Add captions and test them on both players.
- Set budget and usage alarms.
- Document the reprocessing runbook.
- Run a load test against the CDN, not the origin.
Where teams get it wrong
- A public S3 bucket “just for now”.
- Streaming MP4 files from the application server.
- CloudFront keys or AWS credentials in client-side JavaScript.
- No lifecycle policy, so storage grows forever.
- Transcoding the same video on every request.
- One 4K rendition for a catalogue that is mostly watched on phones.
- No retry or alert when a transcode fails.
- No cost alarm until the invoice arrives.
- Ignoring captions and accessibility.
None of this is glamorous. That’s the point. A good streaming platform is measured by how rarely anyone has to think about it.
Keep it boring
Start with the five-piece pipeline, keep the origin private, make access short-lived, and instrument the parts that cost money. You can add DRM, low-latency live, or multi-region later. You can’t easily undo a public bucket or a year of unbudgeted egress.
Direct answers
Questions people actually ask
Short, checkable answers. Every entry here is also published as structured FAQ data for search and answer engines.
Can I use S3 presigned URLs directly instead of CloudFront?
For low-volume internal video, yes. For a subscriber product, CloudFront is usually worth it because it caches segments at the edge, reduces origin load, and gives you central access control. Direct S3 URLs also tend to be long and awkward to manage in a player.
Is a signed CloudFront URL the same as DRM?
No. Signed URLs control who can request a file for a period of time. DRM adds encryption and license management that can survive a determined user capturing the stream. If your content licensing requires DRM, plan for it separately.
How long does this architecture take to build?
A basic VOD pipeline with upload, transcode, CDN, signed playback, and monitoring can take a few weeks for an experienced team. The longer work is usually the admin experience, retries, analytics, and migration of existing content.
Do I need multi-region?
Usually not at the start. CloudFront already delivers from edge locations. Multi-region origin adds complexity and cost. Add it when you have a specific availability or latency requirement that a single well-architected region cannot meet.
References
Sources and further reading
- Zynitex — AWS streaming project
Raykulam and Shaligram Healing architecture.
Compliance notes are operational guidance, not legal or tax advice. Rules and thresholds change — confirm your position with a qualified professional.
Written by
Zynitex Engineering
We design and build the systems behind Zynibill, Invoice Studio, and client platforms like Regalia, Kristive, and ElyteSpark. These notes are the things we wish we had read before shipping them.
More about ZynitexKeep reading
Related notes
The LMS mistakes that only show up after launch
Role design, video delivery, progress semantics, payments, certificates, and student data. The decisions that determine whether an LMS survives its first cohort.
27 October 2026·10 minEngineeringNext.js performance in production: fix the 20% users feel
Field data instead of Lighthouse theatre. The hero image, client boundaries, third-party scripts, and caching decisions that change how a Next.js site feels.
29 September 2026·9 minEngineeringShipping AI features without shipping a liability
Demos are easy. Production AI needs an eval set, guardrails, privacy rules, cost controls, and a fallback. A field guide from two AI products we shipped.
9 October 2026·10 min