← Blog/CDN & Infrastructure

Why Your HLS Stream Randomly Fails with CORS Errors (and How to Fix CloudFront)

Published 2024-06-03·7 min read·By VideoLab

Why perfectly configured HLS streams suddenly throw CORS errors, and how CDN caching behavior is usually to blame.

The Phantom CORS Error

Here is a scenario that drove me absolutely insane:

1. I set up an HLS stream on AWS S3 and served it via CloudFront.

2. I configured my S3 bucket's CORS policy to allow GET and OPTIONS from *.

3. I tested it in dev. It worked perfectly.

4. Three days later, users reported that the video was broken.

5. I checked the console: Cross-Origin Request Blocked.

I checked the headers. The Access-Control-Allow-Origin header was missing from a single .ts segment. But when I cleared my browser cache and refreshed, it worked again! What was going on?

The Root Cause: Origin-Blind Caching

I finally figured out that this happens because of how CDNs like CloudFront cache HTTP responses.

When a browser requests an HLS segment via a tag or hls.js, it includes an Origin header. S3 sees this, and responds with the segment *plus* the Access-Control-Allow-Origin header. CloudFront caches this response. Everything is fine.

However, if a bot, or a native app requested that exact same .ts segment directly (e.g., pasting the URL into the address bar), no Origin header was sent.

S3 saw a request with no Origin, so it didn't bother attaching CORS headers to the response. CloudFront then cached this CORS-less response.

The next time my web player requested that segment, CloudFront served the cached, CORS-less version. The browser saw the missing Access-Control-Allow-Origin header and immediately killed the playback.

The Fix: Whitelist the Origin Header

To fix this, I had to tell CloudFront to treat requests with different Origin headers as completely separate cacheable objects.

In the CloudFront Distribution settings:

1. Go to the Behaviors tab and edit the behavior for your video files (e.g., Default (*)).

2. Scroll down to Cache key and origin requests.

3. Under Headers, choose "Include the following headers".

4. Add Origin to the list of custom headers.

*(If you are using CloudFront Cache Policies, you need to create a new Cache Policy that includes the Origin header in the Cache Key, and attach it to your Behavior).*

The Downside: Cache Fragmentation

By adding Origin to the cache key, CloudFront began caching separate copies of my video for *every different domain* that embedded it.

If site-a.com and site-b.com both played my video, CloudFront fetched the segment from S3 twice. This slightly reduced my cache hit ratio and increased S3 egress costs.

However, for my use case, this was an acceptable trade-off to completely eliminate the phantom CORS bug.

Alternative: S3 Lambda@Edge

If I needed to maximize my cache hit ratio and didn't want to fragment my cache by Origin, the alternative would have been to write a small Lambda@Edge function on the Viewer Response trigger that blindly injects Access-Control-Allow-Origin: * onto every single response leaving CloudFront, regardless of what S3 returned.

Try VideoLab Free →

Put this knowledge to use. Test your video streams, debug errors, and export diagnostics — all in your browser.

Open VideoLab ▶