Nginx übernimmt die Dateiübertragung: Mit X-Accel-Redirect die Download-Leistung in Django steigern

Die meisten Django-Anwendungen liefern geschützte Dateien (z. B. nur für eingeloggte Nutzer oder nach dem Kauf verfügbar) über FileResponse oder ähnliche Mechanismen aus. Dabei liest der Python-Prozess die Datei und streamt sie an den Client. Bei geringem Traffic oder in internen Umgebungen ist das meist völlig ausreichend.

Wenn die Zahl der Download-Anfragen jedoch stark ansteigt, wird der Anwendungsserver (Python) durch Datei-I/O gebunden: Worker hängen an Übertragungen, und es bleibt weniger Kapazität für Berechtigungsprüfungen, Geschäftslogik oder API-Antworten. Die Lösung: Django entscheidet, wer herunterladen darf – und Nginx übernimmt die eigentliche Dateiübertragung per X-Accel-Redirect.


Warum direktes File-Streaming in Python zum Engpass wird



Der typische Ablauf, wenn Django Dateien selbst ausliefert:

  1. Anfrage empfangen
  2. Berechtigungen prüfen
  3. Datei von Datenträger/Storage lesen
  4. Datei über den Applikationsprozess an den Client streamen

Gerade Schritt 3 und 4 sind teuer:

  • Große Dateien bedeuten lange Übertragungszeiten
  • Viele parallele Downloads binden Worker/Threads/Prozesse
  • Das führt zu langsamen API-Antworten, Timeouts und Skalierungsdruck

Nginx ist dagegen für die Auslieferung statischer Dateien optimiert: Kernel-Optimierungen wie sendfile, effiziente Event-Loops, Buffering und Range-Request-Handling sind genau auf Dateiübertragungen ausgelegt.


Kernidee von X-Accel-Redirect

Django prüft, Nginx liefert aus.

So funktioniert es

  1. Der Client ruft z. B. /download/123 auf.

  2. Django macht DB-Lookup und Berechtigungsprüfung.

  3. Django antwortet mit leerem Body, aber mit Header:

X-Accel-Redirect: /_protected/real/path/to/file.webp

  1. Nginx erkennt den Header, findet die Datei intern und streamt sie direkt an den Client.

Damit bestimmt Django nur die Erlaubnis, während Nginx die Übertragung übernimmt.


Wann diese Methode besonders sinnvoll ist



1) Viele Download-/Bildanfragen mit hoher Parallelität

  • Foren, Messenger, Anhänge, PDF-Downloads
  • Viele Requests bei vergleichsweise einfacher Logik

2) Große Dateien oder starke Nutzung von Range-Requests

  • Video, Audio, große Archive
  • Browser/Player fordern Byte-Ranges an; Nginx verarbeitet das zuverlässig

3) App-Server entlasten und Kosten senken

  • Python-Worker sind teuer (RAM/CPU)
  • Offloading hält den App-Server frei für Business-Logik

Wann man darauf verzichten kann

  • Interne Kommunikation mit geringem Traffic
  • Wenige Datei-Requests, bei denen eher API/DB-Logik limitiert
  • Dateien liegen in S3/Objektspeichern und werden bereits via CDN oder Pre-Signed URLs ausgeliefert

Dann ist FileResponse oft völlig ausreichend – und man kann X-Accel-Redirect später bei Bedarf nachziehen.


Beispiel: Django + Nginx

Web-Request-Flowchart

Nginx-Konfiguration

Der zentrale Punkt ist internal: Ein internal-Location ist nicht direkt vom Client erreichbar, sondern nur über interne Redirects wie X-Accel-Redirect.

# Geschützte Dateien intern ausliefern
location /_protected/ {
    internal;
    alias /var/app/protected_media/;
    sendfile on;
    tcp_nopush on;
    # Optional: Cache-/Header-Kontrolle
    # add_header Cache-Control "private, max-age=0";
}
  • Reale Dateien liegen unter /var/app/protected_media/
  • Öffentliche URLs laufen über Django (z. B. /download/...)
  • Interne Pfade werden unter /_protected/... vereinheitlicht

Django-View-Beispiel

Django prüft nur die Berechtigung, ohne Datei-I/O.

from django.http import HttpResponse, Http404
from django.contrib.auth.decorators import login_required
from django.utils.encoding import iri_to_uri

@login_required
def download(request, file_id):
    # 1) DB-Lookup + Berechtigungsprüfung
    obj = get_file_object_or_404(file_id)  # Beispiel
    if not obj.can_download(request.user):
        raise Http404

    # 2) Internen Pfad für Nginx bauen
    internal_path = f"/_protected/{obj.storage_relpath}"

    # 3) Nur X-Accel-Redirect setzen
    response = HttpResponse()
    response["X-Accel-Redirect"] = iri_to_uri(internal_path)

    # Optional: Dateiname und MIME-Type
    response["Content-Type"] = obj.content_type or "application/octet-stream"
    response["Content-Disposition"] = f'attachment; filename="{obj.download_name}"'

    return response
  • Keine FileResponse(open(...))-I/O im View
  • Worker bleiben frei für andere Requests

Sicherheits-Checkliste

  1. Interne Pfade serverseitig bestimmen Nutzerinput nie direkt in X-Accel-Redirect übernehmen. Sichere relative Pfade aus der DB verwenden oder whitelisten.

  2. internal zwingend setzen Sonst könnte /_protected/... direkt aufgerufen und der Check umgangen werden.

  3. Berechtigung ausschließlich in Django Nginx liefert nur aus – Zugriffskontrolle muss in Django bleiben.


Alternativen mit Drittanbietern

Wenn es passt, kann man Dateien auch direkt über externe Services ausliefern:

  • CDN-Caching für öffentliche Dateien
  • Pre-Signed URLs (z. B. S3) für Objektspeicher

Fazit

X-Accel-Redirect verschiebt die teure Dateiübertragung vom App-Server zu Nginx. Django bleibt für Berechtigungen und Business-Logik zuständig, während Nginx die Dateien effizient streamt. Bei geringem Traffic ist FileResponse eine saubere Lösung – wenn aber die Download-Last steigt und Worker durch Datei-I/O blockieren, ist X-Accel-Redirect eine sehr schnelle und wirksame Optimierung.

Merksatz: „Berechtigungen in Django, Auslieferung in Nginx.“