Die Bereitstellung von HLS (HTTP Live Streaming) über nginx ist das Rückgrat der meisten selbst gehosteten Streaming-Setups — VOD, Catch-up und Live-Restreaming basieren alle darauf. Dieser Leitfaden konfiguriert nginx, um HLS-Segmente effizient bereitzustellen, mit den Caching- und CORS-Headern, die Player tatsächlich benötigen.

Wir gehen davon aus, dass Sie bereits .m3u8-Playlists und .ts- (oder .m4s)-Segmente generieren — zum Beispiel aus der FFmpeg NVENC-Pipeline.

Voraussetzungen

  • Ubuntu 22.04 mit installiertem nginx (apt install -y nginx)
  • HLS-Segmente, die in ein Verzeichnis geschrieben werden (z.B. /var/www/hls)
  • Eine auf den Server zeigende Domain

Schritt 1 — Grundlegender HLS-Location-Block

Bearbeiten Sie Ihre Site-Konfiguration (/etc/nginx/sites-available/streaming):

server {
    listen 80;
    server_name stream.yourdomain.com;

    location /hls/ {
        # Segmente von der Festplatte bereitstellen
        root /var/www;

        # Korrekte MIME-Typen für HLS
        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
            video/iso.segment m4s;
        }

        # CORS — erforderlich für Browser-Player (hls.js, video.js)
        add_header Access-Control-Allow-Origin * always;
        add_header Cache-Control no-cache always;
    }
}

Der Cache-Control: no-cache-Header auf der Playlist ist wichtig — Player müssen die .m3u8 neu abrufen, um neue Segmente zu sehen. Das Caching werden wir als Nächstes verfeinern.

Schritt 2 — Getrenntes Caching: Playlists vs. Segmente

Playlists ändern sich mit jedem Segment; Segmente ändern sich nach dem Schreiben nie. Cachen Sie sie unterschiedlich:

location ~ \.m3u8$ {
    root /var/www;
    add_header Access-Control-Allow-Origin * always;
    add_header Cache-Control "no-cache, no-store" always;
    types { application/vnd.apple.mpegurl m3u8; }
}

location ~ \.(ts|m4s)$ {
    root /var/www;
    add_header Access-Control-Allow-Origin * always;
    # Segmente sind unveränderlich — aggressiv cachen
    add_header Cache-Control "public, max-age=86400" always;
    types {
        video/mp2t ts;
        video/iso.segment m4s;
    }
}

Dies ermöglicht es CDNs und Browsern, die großen .ts-Dateien zu cachen, während die kleine Playlist immer neu überprüft wird.

Schritt 3 — sendfile aktivieren und für Durchsatz optimieren

Streaming ist bandbreitenbegrenzt. Im http {}-Block von /etc/nginx/nginx.conf:

sendfile on;
tcp_nopush on;
tcp_nodelay on;
sendfile_max_chunk 1m;

# Worker-Verbindungen für hohe Parallelität erhöhen
worker_processes auto;
events {
    worker_connections 8192;
    use epoll;
    multi_accept on;
}

# Größere Ausgabepuffer für große Segmente
output_buffers 4 256k;

sendfile on ermöglicht es dem Kernel, Segmentdateien direkt von der Festplatte zum Socket zu kopieren, ohne den Umweg über den Userspace zu nehmen — ein großer Vorteil für die Bereitstellung von Segmenten mit hoher Parallelität.

Schritt 4 — HTTPS hinzufügen (Player benötigen es zunehmend)

Browser blockieren gemischte Inhalte, daher kann eine Seite über HTTPS kein HLS über HTTP abrufen. Holen Sie sich ein Zertifikat mit Certbot:

apt install -y certbot python3-certbot-nginx
certbot --nginx -d stream.yourdomain.com

Certbot schreibt Ihren Server-Block um, um auf Port 443 mit dem Zertifikat zu lauschen. Überprüfen Sie die automatische Verlängerung:

certbot renew --dry-run

Schritt 5 — HTTP/2 aktivieren (und HTTP/3, wenn möglich)

HTTP/2 multiplexiert Segmentanfragen über eine einzige Verbindung — sinnvoll für HLS, wo ein Player viele kleine Dateien abruft:

listen 443 ssl;
http2 on;

Für HTTP/3 (QUIC) benötigen Sie nginx, das mit dem http_v3-Modul oder einem Build wie nginx-quic erstellt wurde. HTTP/3 hilft Playern in verlustbehafteten Mobilfunknetzen am meisten.

Schritt 6 — Den „Thundering Herd“-Effekt bei Live-Streams handhaben

Wenn ein Segment eines beliebten Live-Streams verfügbar wird, fordern Tausende von Playern es nahezu gleichzeitig an. Ohne Vorsichtsmaßnahmen würden sie alle gleichzeitig auf die Festplatte zugreifen. Aktivieren Sie das Open-File-Caching:

open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

Dies speichert Dateideskriptoren und Metadaten im Cache, sodass die zweite bis tausendste Anfrage für ein heißes Segment aus dem Cache bedient wird und nicht durch ein frisches stat()+open().

Schritt 7 — Testen

Neu laden und überprüfen:

nginx -t && systemctl reload nginx
curl -I https://stream.yourdomain.com/hls/stream.m3u8

Sie möchten 200 OK, Content-Type: application/vnd.apple.mpegurl, Access-Control-Allow-Origin: * und Cache-Control: no-cache auf der Playlist sehen. Laden Sie sie in einem Browser mit hls.js oder in VLC, um die Wiedergabe zu bestätigen.

Fehlerbehebung

CORS-Fehler in der Browserkonsole: Der Access-Control-Allow-Origin-Header erreicht den Client nicht. Überprüfen Sie, ob er sowohl für die .m3u8- als auch für die .ts-Locations gesetzt ist und dass kein Upstream-Proxy ihn entfernt.

Player stockt / puffert neu: Normalerweise kann die Segmentlieferung nicht mithalten. Überprüfen Sie die Bandbreite mit iftop; wenn der Port ausgelastet ist, benötigen Sie einen größeren Uplink. Vergewissern Sie sich auch, dass hls_time (Segmentdauer) nicht zu kurz ist — 6s ist ein sicherer Standardwert.

404 bei Segmenten, aber Playlist lädt: Der root-Pfad ist falsch, oder Segmente werden gelöscht (kurze hls_list_size), bevor Player sie anfordern. Erhöhen Sie hls_list_size in Ihrem FFmpeg-Befehl.

Nächste Schritte

Für die geografisch verteilte Bereitstellung platzieren Sie eine CDN-Edge-Flotte vor diesem Origin. Für die Transkodierungsseite siehe den FFmpeg NVENC-Leitfaden. Und dimensionieren Sie Ihre Origin-Bandbreite mit unserem Port-Geschwindigkeitsleitfaden.