Back to Blog

Coming Soon

Editorial Team
9/16/2025
Article
M3U8

Complete M3U8 CORS Error Solutions: The Ultimate Guide to Cross-Origin Issues

CORS (Cross-Origin Resource Sharing) errors are among the most common issues in M3U8 video playback. When you see errors like "Access to XMLHttpRequest has been blocked by CORS policy", don't panic. This article provides comprehensive solutions.

Understanding CORS Errors

What is CORS?

CORS is a browser security mechanism that controls how web pages from one domain can access resources from another domain. When your website tries to load M3U8 videos from a different domain, it may trigger CORS restrictions.

CORS Error Manifestations

// Common CORS error messages
Access to XMLHttpRequest at 'https://cdn.example.com/video.m3u8' 
from origin 'https://yoursite.com' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

// Or
Cross-Origin Read Blocking (CORB) blocked cross-origin response 
https://cdn.example.com/video.m3u8 with MIME type application/vnd.apple.mpegurl.

Root Causes of CORS Errors

graph LR
    A[Your Website yoursite.com] -->|Request Video| B[CDN Server cdn.example.com]
    B -->|Missing CORS Headers| C[Browser Blocks]
    C -->|CORS Error| D[Video Cannot Play]

Quick CORS Problem Diagnosis

Using Browser Developer Tools

// Check CORS issues in console
fetch('https://cdn.example.com/video.m3u8')
  .then(response => {
    console.log('CORS Headers:', response.headers.get('Access-Control-Allow-Origin'));
    console.log('Content-Type:', response.headers.get('Content-Type'));
  })
  .catch(error => {
    console.error('CORS Error:', error);
  });

Using curl Commands

# Check response headers
curl -I https://cdn.example.com/video.m3u8

# Check CORS-related headers
curl -H "Origin: https://yoursite.com" \
     -H "Access-Control-Request-Method: GET" \
     -H "Access-Control-Request-Headers: X-Requested-With" \
     -X OPTIONS \
     https://cdn.example.com/video.m3u8 -I

Solution 1: Server-Side Configuration

1. Nginx Configuration

# Add to nginx.conf or site configuration file
location ~* \.(m3u8|ts)$ {
    # Allow all domains (use cautiously in production)
    add_header Access-Control-Allow-Origin *;
    
    # Or specify specific domain
    add_header Access-Control-Allow-Origin https://yoursite.com;
    
    # Allowed request methods
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    
    # Allowed request headers
    add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
    
    # Allow credentials
    add_header Access-Control-Allow-Credentials true;
    
    # Preflight request cache time
    add_header Access-Control-Max-Age 1728000;
    
    # Handle OPTIONS preflight requests
    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

2. Apache Configuration

# Add to .htaccess or httpd.conf
<FilesMatch "\.(m3u8|ts)$">
    # Allow cross-origin
    Header set Access-Control-Allow-Origin "*"
    
    # Or specify domain
    Header set Access-Control-Allow-Origin "https://yoursite.com"
    
    # Other CORS headers
    Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
    Header set Access-Control-Allow-Headers "Origin, Content-Type, Accept, Range"
    Header set Access-Control-Allow-Credentials "true"
</FilesMatch>

# Handle preflight requests
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]

3. Node.js/Express Configuration

const express = require('express');
const cors = require('cors');
const app = express();

// Method 1: Using cors middleware
app.use(cors({
    origin: 'https://yoursite.com',  // Or use '*' to allow all
    credentials: true,
    methods: ['GET', 'POST', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Range'],
    exposedHeaders: ['Content-Length', 'Content-Range']
}));

// Method 2: Manual CORS headers
app.use((req, res, next) => {
    // Dynamically set allowed origins
    const allowedOrigins = [
        'https://yoursite.com',
        'https://app.yoursite.com',
        'http://localhost:3000'
    ];
    
    const origin = req.headers.origin;
    if (allowedOrigins.includes(origin)) {
        res.setHeader('Access-Control-Allow-Origin', origin);
    }
    
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Range');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Max-Age', '86400');
    
    if (req.method === 'OPTIONS') {
        res.sendStatus(204);
    } else {
        next();
    }
});

// Serve M3U8 files
app.get('/videos/:id.m3u8', (req, res) => {
    res.type('application/vnd.apple.mpegurl');
    res.sendFile(path.join(__dirname, 'videos', `${req.params.id}.m3u8`));
});

Solution 2: Using Proxy Server

1. Create Node.js Proxy

const express = require('express');
const axios = require('axios');
const app = express();

// CORS proxy endpoint
app.get('/proxy/m3u8', async (req, res) => {
    const targetUrl = req.query.url;
    
    if (!targetUrl) {
        return res.status(400).json({ error: 'URL parameter required' });
    }
    
    try {
        // Get M3U8 content
        const response = await axios.get(targetUrl, {
            responseType: 'text',
            headers: {
                'User-Agent': req.headers['user-agent'],
                'Referer': req.headers.referer || targetUrl
            }
        });
        
        // Set CORS headers
        res.header('Access-Control-Allow-Origin', '*');
        res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
        res.header('Content-Type', 'application/vnd.apple.mpegurl');
        
        // Process M3U8 content, convert relative paths to absolute
        let content = response.data;
        const baseUrl = new URL(targetUrl).origin;
        
        // Replace relative paths
        content = content.replace(/^(?!#)(.+\.ts)$/gm, (match, p1) => {
            if (p1.startsWith('http')) return p1;
            return `${baseUrl}/${p1}`;
        });
        
        res.send(content);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

// Proxy TS segments
app.get('/proxy/ts', async (req, res) => {
    const targetUrl = req.query.url;
    
    try {
        const response = await axios.get(targetUrl, {
            responseType: 'stream'
        });
        
        res.header('Access-Control-Allow-Origin', '*');
        res.header('Content-Type', 'video/MP2T');
        
        response.data.pipe(res);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.listen(3000, () => {
    console.log('CORS proxy server running on port 3000');
});

2. Using Cloudflare Workers

// Cloudflare Workers script
addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
    const url = new URL(request.url);
    const targetUrl = url.searchParams.get('url');
    
    if (!targetUrl) {
        return new Response('Missing URL parameter', { status: 400 });
    }
    
    // Fetch target resource
    const response = await fetch(targetUrl, {
        headers: {
            'User-Agent': request.headers.get('User-Agent'),
            'Referer': request.headers.get('Referer')
        }
    });
    
    // Create new response with CORS headers
    const modifiedResponse = new Response(response.body, response);
    modifiedResponse.headers.set('Access-Control-Allow-Origin', '*');
    modifiedResponse.headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
    modifiedResponse.headers.set('Cache-Control', 'public, max-age=3600');
    
    // Process M3U8 content
    if (targetUrl.includes('.m3u8')) {
        const text = await response.text();
        const baseUrl = new URL(targetUrl).origin;
        
        // Convert relative URLs to absolute
        const modifiedText = text.replace(/^(?!#)(.+\.ts)$/gm, (match, p1) => {
            if (p1.startsWith('http')) return p1;
            return `${url.origin}/?url=${encodeURIComponent(baseUrl + '/' + p1)}`;
        });
        
        return new Response(modifiedText, {
            headers: modifiedResponse.headers
        });
    }
    
    return modifiedResponse;
}

Solution 3: Frontend Handling

1. Using HLS.js Configuration

import Hls from 'hls.js';

// Configure HLS.js to handle CORS
const config = {
    // Enable CORS credentials
    xhrSetup: function(xhr, url) {
        xhr.withCredentials = false; // If cookies not needed
        // Add custom headers
        xhr.setRequestHeader('X-Custom-Header', 'value');
    },
    
    // Use fetch instead of XHR
    loader: class CustomLoader extends Hls.DefaultConfig.loader {
        load(context, config, callbacks) {
            const { url, responseType } = context;
            
            fetch(url, {
                method: 'GET',
                mode: 'cors', // Explicitly specify CORS mode
                credentials: 'omit', // Don't send cookies
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                return responseType === 'arraybuffer' 
                    ? response.arrayBuffer() 
                    : response.text();
            })
            .then(data => {
                callbacks.onSuccess({
                    url,
                    data
                }, { }, context);
            })
            .catch(error => {
                callbacks.onError({
                    code: 0,
                    text: error.message
                }, context);
            });
        }
    }
};

const hls = new Hls(config);

2. Using Video.js

import videojs from 'video.js';
import 'videojs-contrib-hls';

// Configure Video.js
const player = videojs('my-video', {
    html5: {
        hls: {
            withCredentials: false,
            // Override XHR
            beforeRequest: function(options) {
                // Modify request options
                options.headers = options.headers || {};
                options.headers['X-Custom-Header'] = 'value';
                
                // Use proxy URL
                if (options.uri.includes('example.com')) {
                    options.uri = `/proxy?url=${encodeURIComponent(options.uri)}`;
                }
                
                return options;
            }
        }
    }
});

3. Create Universal CORS Handler

class CORSHandler {
    constructor(proxyUrl = '/proxy') {
        this.proxyUrl = proxyUrl;
    }
    
    // Detect CORS support
    async checkCORS(url) {
        try {
            const response = await fetch(url, {
                method: 'HEAD',
                mode: 'cors'
            });
            return response.ok;
        } catch (error) {
            console.log('CORS not supported for:', url);
            return false;
        }
    }
    
    // Get proxy URL
    getProxyUrl(originalUrl) {
        return `${this.proxyUrl}?url=${encodeURIComponent(originalUrl)}`;
    }
    
    // Process M3U8 content
    async processM3U8(url) {
        const supportsCORS = await this.checkCORS(url);
        const targetUrl = supportsCORS ? url : this.getProxyUrl(url);
        
        const response = await fetch(targetUrl);
        let content = await response.text();
        
        if (!supportsCORS) {
            // Process segment URLs in M3U8
            const baseUrl = new URL(url).origin;
            content = content.split('\n').map(line => {
                if (line.endsWith('.ts') && !line.startsWith('http')) {
                    const tsUrl = `${baseUrl}/${line}`;
                    return this.getProxyUrl(tsUrl);
                }
                return line;
            }).join('\n');
        }
        
        return content;
    }
    
    // Create Blob URL
    createBlobUrl(content) {
        const blob = new Blob([content], { 
            type: 'application/vnd.apple.mpegurl' 
        });
        return URL.createObjectURL(blob);
    }
}

// Usage example
const corsHandler = new CORSHandler();

async function playM3U8(videoUrl) {
    try {
        const content = await corsHandler.processM3U8(videoUrl);
        const blobUrl = corsHandler.createBlobUrl(content);
        
        // Play using blob URL
        if (Hls.isSupported()) {
            const hls = new Hls();
            hls.loadSource(blobUrl);
            hls.attachMedia(document.getElementById('video'));
        }
    } catch (error) {
        console.error('Failed to play video:', error);
    }
}

Solution 4: CDN Configuration

1. AWS CloudFront Configuration

# CloudFront behavior configuration
Behaviors:
  - PathPattern: "*.m3u8"
    TargetOriginId: MyOrigin
    ViewerProtocolPolicy: allow-all
    ResponseHeadersPolicyId: !Ref ResponseHeadersPolicy

ResponseHeadersPolicy:
  CorsConfig:
    AccessControlAllowOrigins:
      Items: ["*"]
    AccessControlAllowMethods:
      Items: ["GET", "HEAD", "OPTIONS"]
    AccessControlAllowHeaders:
      Items: ["*"]
    AccessControlExposeHeaders:
      Items: ["Content-Length", "Content-Range"]
    AccessControlMaxAgeSec: 86400

2. Fastly Configuration

# Fastly VCL configuration
sub vcl_recv {
  if (req.url ~ "\.(m3u8|ts)$") {
    set req.http.Access-Control-Allow-Origin = "*";
  }
}

sub vcl_deliver {
  if (req.url ~ "\.(m3u8|ts)$") {
    set resp.http.Access-Control-Allow-Origin = "*";
    set resp.http.Access-Control-Allow-Methods = "GET, OPTIONS";
    set resp.http.Access-Control-Allow-Headers = "Content-Type, Range";
  }
}

3. Cloudflare Configuration

// Page Rules or Transform Rules
// Add response header rules
URL Pattern: *example.com/*.m3u8
Add Response Header: Access-Control-Allow-Origin = *

Solution 5: Using Service Worker

// sw.js - Service Worker file
self.addEventListener('fetch', event => {
    const url = new URL(event.request.url);
    
    // Intercept M3U8 and TS requests
    if (url.pathname.endsWith('.m3u8') || url.pathname.endsWith('.ts')) {
        event.respondWith(
            fetch(event.request.url, {
                mode: 'no-cors' // Bypass CORS check
            })
            .then(response => {
                // Create new response with CORS headers
                const headers = new Headers(response.headers);
                headers.set('Access-Control-Allow-Origin', '*');
                headers.set('Content-Type', 
                    url.pathname.endsWith('.m3u8') 
                        ? 'application/vnd.apple.mpegurl' 
                        : 'video/MP2T'
                );
                
                return new Response(response.body, {
                    status: response.status,
                    statusText: response.statusText,
                    headers: headers
                });
            })
            .catch(error => {
                console.error('Service Worker fetch error:', error);
                return new Response('Error fetching resource', { status: 500 });
            })
        );
    }
});

// Register Service Worker
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(registration => console.log('SW registered'))
        .catch(error => console.error('SW registration failed:', error));
}

Debugging Tips

1. Chrome Developer Tools

// Disable CORS check (for development only)
// Windows
chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security

// macOS
open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security

// Linux
google-chrome --disable-web-security --user-data-dir="/tmp/chrome_dev_test"

2. Using Local Proxy Tools

# Using local-cors-proxy
npm install -g local-cors-proxy
lcp --proxyUrl https://example.com --port 8010

# Using cors-anywhere
npm install cors-anywhere
node server.js

3. Test Script

<!DOCTYPE html>
<html>
<head>
    <title>CORS Test</title>
</head>
<body>
    <h1>M3U8 CORS Test</h1>
    <div id="result"></div>
    
    <script>
        async function testCORS(url) {
            const resultDiv = document.getElementById('result');
            
            try {
                // Test 1: Simple request
                const response1 = await fetch(url, { mode: 'cors' });
                resultDiv.innerHTML += `✅ Simple request succeeded<br>`;
                
                // Test 2: Preflight request
                const response2 = await fetch(url, {
                    mode: 'cors',
                    method: 'OPTIONS'
                });
                resultDiv.innerHTML += `✅ Preflight request succeeded<br>`;
                
                // Check response headers
                const headers = {};
                response1.headers.forEach((value, key) => {
                    if (key.toLowerCase().includes('access-control')) {
                        headers[key] = value;
                    }
                });
                
                resultDiv.innerHTML += `<pre>${JSON.stringify(headers, null, 2)}</pre>`;
                
            } catch (error) {
                resultDiv.innerHTML += `❌ CORS Error: ${error.message}<br>`;
            }
        }
        
        // Test your M3U8 URL
        testCORS('https://example.com/video.m3u8');
    </script>
</body>
</html>

Best Practices

1. Secure CORS Configuration

// Don't do this (too permissive)
res.header('Access-Control-Allow-Origin', '*');

// Recommended approach (whitelist control)
const allowedOrigins = [
    'https://yoursite.com',
    'https://app.yoursite.com'
];

const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
}

2. Cache CORS Preflight

# Nginx configuration optimization
location ~* \.(m3u8|ts)$ {
    # Cache preflight requests for 24 hours
    add_header Access-Control-Max-Age 86400;
    
    # Only return CORS headers when needed
    if ($http_origin ~* (https?://.*\.yoursite\.com)) {
        add_header Access-Control-Allow-Origin $http_origin;
    }
}

3. Monitor CORS Errors

// Frontend error monitoring
window.addEventListener('unhandledrejection', event => {
    if (event.reason && event.reason.message && 
        event.reason.message.includes('CORS')) {
        // Report CORS error
        console.error('CORS Error detected:', {
            url: event.reason.url,
            message: event.reason.message,
            timestamp: new Date().toISOString()
        });
        
        // Send to monitoring service
        fetch('/api/errors', {
            method: 'POST',
            body: JSON.stringify({
                type: 'CORS',
                error: event.reason.message
            })
        });
    }
});

Conclusion

While CORS errors are common, they can be completely resolved with proper configuration and handling strategies. Key points:

  1. Understand CORS Mechanism - Know why CORS errors occur
  2. Choose Appropriate Solution - Select server-side, CDN, or frontend solutions based on control permissions
  3. Security First - Don't blindly use Access-Control-Allow-Origin: *
  4. Performance Optimization - Use caching to reduce preflight requests
  5. Backup Plan - Prepare proxy service as fallback

If you don't want to deal with complex CORS configurations, use M3U8 Player online player, which has already handled common CORS issues, letting you focus on the video content itself.