Не позволяйте Django напрямую отдавать файлы: повышаем производительность загрузки с помощью X-Accel-Redirect

Большинство сервисов Django используют FileResponse для отдачи защищённых файлов (только авторизованным пользователям, после оплаты и т.д.). При небольшом объёме трафика и внутренней коммуникации такой подход вполне достаточен.

Но когда запросы на файлы резко возрастают, приложение начинает «залипать» в процессе отдачи. Python‑сервер вынужден заниматься чтением файлов, а значит, не успевает выполнять остальные задачи (проверка прав, бизнес‑логика, обработка API). В таких случаях удобно делегировать передачу файлов Nginx, оставив Django только за проверкой прав. Это и есть принцип X‑Accel‑Redirect.


Почему прямой вывод файлов в Python приводит к узким местам?



Типичный поток:

  1. Приём запроса
  2. Проверка прав
  3. Чтение файла с диска/хранилища
  4. Передача данных через приложение (streaming)

Проблема в шагах 3‑4: они «тяжёлые».

  • Чем больше файл, тем дольше его передача
  • При росте одновременных загрузок воркеры/потоки блокируются
  • В итоге увеличивается время ответа API, появляются тайм‑ауты и нагрузка на сервер

Nginx, напротив, оптимизирован под статическую отдачу: sendfile, эффективный цикл событий, буферизация и поддержка диапазонных запросов.


Суть X‑Accel‑Redirect

Django отвечает только за проверку, Nginx — за передачу.

Как это работает

  1. Клиент запрашивает /download/123
  2. Django делает только запрос к БД и проверку прав
  3. Django возвращает пустое тело с заголовком: X-Accel-Redirect: /_protected/real/path/to/file.webp
  4. Nginx видит этот заголовок, ищет файл по внутреннему пути и отдаёт его клиенту напрямую

Таким образом Django не читает файл, а лишь подтверждает право доступа.


Когда стоит использовать X‑Accel‑Redirect?



1) Высокая частота и параллельность запросов

  • Сообщества, мессенджеры, отчёты PDF
  • Паттерн «много запросов, простая логика» — X‑Accel‑Redirect блестит

2) Большие файлы и важность диапазонных запросов

  • Видео, аудио, архивы
  • Браузер/плеер запрашивает Range → Nginx обрабатывает это надёжно

3) Снижение стоимости приложения

  • Python‑воркеры дорогие (память/CPU)
  • Перенос передачи файлов в прокси‑слой освобождает ресурсы

Когда можно обойтись без X‑Accel‑Redirect?

  • Внутренние сервисы, низкая нагрузка
  • Мало запросов, логика в API является узким местом
  • Файлы находятся в S3 и уже обслуживаются CDN/пресайн‑URL

В этих случаях FileResponse вполне подходит.


Пример реализации: Django + Nginx

Поток обработки веб‑запроса

Конфигурация Nginx

Ключевой директива — internal. Путь, помеченный как internal, недоступен напрямую из браузера, но может быть использован через X‑Accel‑Redirect.

# Внутренний эндпоинт для защищённых файлов
location /_protected/ {
    internal;

    # Физический путь к файлам
    alias /var/app/protected_media/;

    # Оптимизация
    sendfile on;
    tcp_nopush on;

    # При необходимости можно задать заголовки кэширования
    # add_header Cache-Control "private, max-age=0";
}
  • Предполагается, что файлы находятся в /var/app/protected_media/
  • Внешний URL остаётся /download/... (обрабатывается Django)
  • Внутренний путь всегда / _protected/...

Пример представления Django

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) Получаем объект и проверяем права
    obj = get_file_object_or_404(file_id)
    if not obj.can_download(request.user):
        raise Http404

    # 2) Формируем внутренний путь
    internal_path = f"/_protected/{obj.storage_relpath}"

    # 3) Возвращаем ответ только с заголовком
    response = HttpResponse()
    response["X-Accel-Redirect"] = iri_to_uri(internal_path)

    # (опционально) имя файла и тип контента
    response["Content-Type"] = obj.content_type or "application/octet-stream"
    response["Content-Disposition"] = f'attachment; filename="{obj.download_name}"'

    return response
  • Нет чтения файла через FileResponse
  • Время обработки запроса минимально, воркеры не блокируются

Checklist безопасности

1) Внутренний путь определяется сервером

  • Не допускайте, чтобы пользователь мог задать путь вида /_protected/../../etc/passwd
  • Храните только безопасные относительные пути в БД или используйте белый список

2) internal в Nginx обязательно

  • Без internal любой может напрямую обратиться к / _protected/...

3) Проверка прав только в Django

  • Nginx выступает только как транспортный механизм

Альтернативы через сторонние сервисы

Если бюджет позволяет, можно отдавать файлы напрямую из облачного хранилища.

  • CDN‑кеш: для публичных файлов CDN обычно эффективнее, чем Nginx
  • Пресайн‑URL (S3 и др.): проще, чем X‑Accel‑Redirect, если файлы уже находятся в облаке

Итоги

В итоге отдача файлов через Nginx с X‑Accel‑Redirect даёт заметный прирост производительности и снижает нагрузку на приложение. Это особенно важно при больших объёмах и высокой параллельности запросов. Если трафик невелик, FileResponse остаётся простым и надёжным решением.

Запомните: «Права проверяет Django, отдаёт Nginx».