When you attempt to play an M3U8 stream in a web browser (using libraries like hls.js, Video.js, or native HTML5 video), the player executes asynchronous HTTP requests (XHR or Fetch API) to retrieve the manifest and media segments. If your video files are hosted on a different domain, subdomain, or port than the webpage containing the player, the browser's security model dictates that a Cross-Origin Resource Sharing (CORS) check must pass.
Understanding the CORS Problem in HLS
Without proper CORS headers, the browser blocks the JavaScript player from reading the M3U8 file contents. You might be able to download the file by pasting the URL directly into your address bar, but the web player will fail with a classic console error:
Access to XMLHttpRequest at 'https://cdn.example.com/stream.m3u8' from origin 'https://www.yoursite.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
To fix this, your streaming server or CDN must explicitly permit the player's domain to read the files by returning specific HTTP headers.
The Essential CORS Headers
To achieve flawless playback, your server needs to respond with the following headers when serving `.m3u8`, `.ts`, `.mp4`, `.m4s`, `.vtt`, and `.key` files:
Access-Control-Allow-Origin: Specifies which domains can access the resources. Use*for public streams, or specify a precise origin (e.g.,https://www.yoursite.com) for private streams.Access-Control-Allow-Methods: Specifies allowed HTTP methods (e.g.,GET, HEAD, OPTIONS).Access-Control-Allow-Headers: Specifies allowed request headers. HLS players often send custom headers or standard ones likeRangeandAccept.Access-Control-Expose-Headers: (Optional but recommended) Allows the player to read specific response headers, such asContent-LengthorDate.
Nginx Configuration for HLS
Nginx is widely used as an edge server or caching layer for video delivery. Here is a robust configuration block designed specifically for serving HLS content. You can place this within your server or location block.
location /hls/ {
# Define the directory where your media files live
alias /var/www/hls/;
# Optional: Enable byte-range requests for seeking
add_header Accept-Ranges bytes;
# CORS configuration
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
# Allow the 'Range' header, crucial for HLS segment fetching
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# Max cache time for the preflight request (20 days)
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'HEAD') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
Nginx Preflight (OPTIONS)
Browsers often send an HTTP OPTIONS request (a "preflight") before the actual GET request when custom headers are involved. The Nginx config explicitly handles this by immediately returning an HTTP 204 response with the allowed CORS constraints.
Apache Configuration for HLS
For Apache web servers, CORS headers can be applied using the .htaccess file or directly in the virtual host configuration. You must ensure that mod_headers is enabled (a2enmod headers).
Add the following directives to your configuration:
<IfModule mod_headers.c>
<FilesMatch "\.(m3u8|ts|mp4|m4s|vtt|key)$">
# Enable CORS for public access
Header set Access-Control-Allow-Origin "*"
# Define allowed methods
Header set Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
# Define allowed headers (Crucial for HLS players)
Header set Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range"
# Expose specific headers to the Javascript client
Header set Access-Control-Expose-Headers "Content-Length,Content-Range"
</FilesMatch>
</IfModule>
This Apache configuration utilizes FilesMatch to target only specific HLS-related file extensions, ensuring that CORS policies are not unnecessarily applied to unrelated resources like HTML or PHP files.
Troubleshooting Common CORS Issues
1. The "Multiple Access-Control-Allow-Origin" Error
If you see an error stating that the response contains multiple Access-Control-Allow-Origin values, it means your headers are being added twice. This often happens if you configure CORS at both the application level (e.g., in Node.js or PHP) and the web server level (Nginx/Apache), or if you use a CDN (like Cloudflare or CloudFront) that also appends CORS headers. Ensure only one layer is responsible for injecting these headers.
2. Missing OPTIONS Handling
If your HLS stream requires authentication (passing a token in a custom header like Authorization: Bearer ...), the browser will trigger a preflight OPTIONS request. If your server rejects the OPTIONS request (e.g., returning a 401 Unauthorized or 403 Forbidden), the subsequent GET request will never execute. Ensure your server explicitly handles and allows OPTIONS requests without requiring authentication.
3. Caching Stale CORS Headers
When you update your Nginx or Apache configuration, intermediate caches or CDNs might hold onto the old, broken responses. Always purge your CDN cache and bypass local browser caches (using Incognito mode or disabling cache in DevTools) after making changes to your CORS configuration.
Verifying Your Configuration
Once applied, you can verify your configuration using the command line with curl:
curl -I -H "Origin: https://freem3u8.com" https://your-server.com/hls/stream.m3u8
Look for the Access-Control-Allow-Origin: * line in the response headers. If it's present, your server is properly configured, and your streams should play smoothly in browser-based players.