Ne laissez pas Django servir les fichiers directement : boostez les performances de téléchargement avec X‑Accel‑Redirect
La plupart des services Django livrent des fichiers protégés (téléchargement réservé aux utilisateurs connectés, accès après paiement, etc.) en lisant le fichier via un FileResponse et en le streamant vers le client. Cela fonctionne bien tant que le trafic est faible ou que la communication reste interne.
Lorsque les requêtes de fichiers explosent, le serveur d’application (Python) se retrouve occupé par la livraison des fichiers, ce qui rend difficile l’exécution des autres logiques (vérification d’autorisation, traitement métier, API, etc.). C’est ici qu’intervient la technique X‑Accel‑Redirect : Django vérifie les droits, Nginx s’occupe de la transmission.
Pourquoi l’envoi direct par Python crée un goulot d’étranglement ?
Le flux typique est le suivant :
- Réception de la requête
- Vérification des droits
- Lecture du fichier depuis le disque ou le stockage
- Envoi du fichier via le processus Python (streaming)
Les étapes 3 et 4 sont lourdes :
- Plus le fichier est gros, plus le temps de transmission est long
- Plus il y a de téléchargements simultanés, plus les workers/threads/processus sont bloqués
- Cela entraîne des retards de réponse API, des time‑outs et la nécessité d’augmenter les ressources serveur
Nginx, quant à lui, est optimisé pour la diffusion de fichiers statiques grâce à sendfile, une boucle d’événements efficace, le buffering et la prise en charge des requêtes Range.
L’idée clé de X‑Accel‑Redirect
Django fait la vérification, Nginx fait la transmission.
Fonctionnement
- Le client demande
/download/123 - Django effectue la requête DB et la vérification des droits
- Django renvoie une réponse vide avec l’en-tête suivant :
X-Accel-Redirect: /_protected/real/path/to/file.webp - Nginx lit cet en‑tête, trouve le fichier en interne et le transmet directement au client
Ainsi, Django ne lit pas le contenu du fichier, il se contente de décider si l’utilisateur a le droit de le télécharger.
Quand cette approche est particulièrement utile
1) Services à forte charge de téléchargement ou d’images
- Communautés, messageries, pièces jointes, rapports PDF
- Modèle « nombreux requêtes, logique simple » → X‑Accel‑Redirect brille
2) Fichiers volumineux ou requêtes Range critiques
- Vidéos, audios, archives
- Les navigateurs ou lecteurs utilisent
Rangepour la lecture ou le téléchargement progressif ; Nginx gère ces transferts de façon plus fiable
3) Réduction des coûts du serveur d’application
- Les workers Python sont coûteux (mémoire/CPU) ; les bloquer sur la transmission est inefficace
- En déléguant la transmission à Nginx, le serveur d’application peut se concentrer sur la logique métier
Quand on peut s’en passer
- Communication interne avec faible trafic
- Peu de requêtes de fichiers, la logique API/DB est le vrai goulot
- Fichiers stockés sur un service externe (S3, etc.) déjà servi par un CDN ou des URL pré‑signées
Dans ces cas, FileResponse reste une solution simple et suffisante.
Exemple d’implémentation : Django + Nginx

Configuration Nginx
L’essentiel est l’attribut internal. Un location marqué internal ne peut être accédé que par une redirection interne via X‑Accel‑Redirect.
# Point interne servant les fichiers protégés
location /_protected/ {
internal;
# Répertoire contenant les fichiers réels
alias /var/app/protected_media/;
# Options de performance (à ajuster selon l’environnement)
sendfile on;
tcp_nopush on;
# Gestion optionnelle du cache/headers
# add_header Cache-Control "private, max-age=0";
}
- On suppose que les fichiers existent sous
/var/app/protected_media/ - L’URL publique est
/download/...via une route Django - Le chemin interne est toujours
/_protected/...
Vue Django
Django vérifie les droits puis renvoie uniquement l’en‑tête.
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) Recherche DB + vérification des droits
obj = get_file_object_or_404(file_id) # Exemple
if not obj.can_download(request.user):
raise Http404
# 2) Construction du chemin interne (sous /_protected/)
internal_path = f"/_protected/{obj.storage_relpath}"
# 3) En‑tête X‑Accel‑Redirect, corps vide
response = HttpResponse()
response["X-Accel-Redirect"] = iri_to_uri(internal_path)
# (Optionnel) Nom de fichier / type MIME
response["Content-Type"] = obj.content_type or "application/octet-stream"
response["Content-Disposition"] = f'attachment; filename="{obj.download_name}"'
return response
Points clés :
- Pas de
FileResponse(open(...))→ pas d’I/O fichier - Le temps de traitement par le worker est réduit, il n’est pas bloqué par la transmission
Checklist de sécurité
1) Le chemin interne doit être déterminé par le serveur
- Empêcher les attaques de type
/_protected/../../etc/passwd - Utiliser uniquement des chemins relatifs sûrs stockés en base ou une liste blanche
2) Le location doit être internal
- Sans
internal, un utilisateur pourrait accéder directement à/_protected/...
3) La logique d’autorisation doit rester dans Django
- Nginx ne fait que transmettre ; toute décision d’accès doit être prise par Django
Alternatives via un service tiers
Si le budget le permet, on peut envisager de servir les fichiers depuis un stockage tiers (S3, etc.).
- CDN : pour les fichiers publics, un CDN avant Nginx est encore plus performant
- URL pré‑signées : pour les objets S3, une URL pré‑signée peut remplacer X‑Accel‑Redirect
Conclusion
En résumé, déléguer la diffusion de fichiers à Nginx via X‑Accel‑Redirect améliore nettement les performances et la scalabilité (ou la capacité de montée en charge), surtout lorsqu’il y a un grand nombre de téléchargements ou des fichiers volumineux. Le serveur d’application reste concentré sur la logique métier, tandis que Nginx, optimisé pour le transfert de fichiers, gère le reste.
Si le trafic est modeste, FileResponse reste une option propre. Mais dès que les requêtes de fichiers explosent, X‑Accel‑Redirect est la solution la plus rapide et la plus efficace.
Mémorisez simplement : « Django vérifie, Nginx transmet ».