Nginx

If you serve Django behind Nginx, then you can delegate the file download service to Nginx and get increased performance:

  • lower resources used by Python/Django workers ;
  • faster download.

See Nginx X-accel documentation [1] for details.

Configure some download view

As an example, let’s consider an application called “myapp”.

settings.py:

INSTALLED_APPS = (
    # ...
    'myapp',
    # ...
)
MYAPP_STORAGE_LOCATION = '/var/www/files/'  # Could be MEDIA_ROOT for public
                                            # files.

This application holds a Document model.

myapp/models.py:

from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.db import models


storage = FileSystemStorage(location=settings.MYAPP_STORAGE_LOCATION)


class Document(models.Model):
    file = models.FileField(storage=storage, upload_to='document')

Notice the storage and upload_to parameters: files for Document model live in /var/www/files/document/ folder.

Then we configured a download view for this model, restricted to authenticated users:

myapp/urls.py:

from django.conf.urls import url, url_patterns
from django.contrib.auth.decorators import login_required

from django_downloadview import ObjectDownloadView

from myapp.models import Document


download = login_required(ObjectDownloadView.as_view(model=Document))

url_patterns = ('',
    url('^document/(?P<pk>[0-9]+/download/$', download, name='download'),
)

As is, Django is to serve the files, i.e. load chunks into memory and stream them.

Nginx is much more efficient for the actual streaming... Let’s use it!

Configure Nginx

See Nginx X-accel documentation [1] for details.

Here is what you could have in /etc/nginx/sites-available/default:

charset utf-8;

# Django-powered service.
upstream frontend {
    server 127.0.0.1:8000 fail_timeout=0;
}

server {
    listen 80 default;

    # File-download proxy.
    #
    # Will serve /var/www/files/myfile.tar.gz when passed URI
    # like /optimized-download/myfile.tar.gz
    #
    # See http://wiki.nginx.org/X-accel
    # and https://github.com/benoitbryon/django-downloadview
    location /optimized-download {
        internal;
        # Location to files on disk.
        # See Django's settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT
        alias /var/www/files/;
    }

    # Proxy to Django-powered frontend.
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://frontend;
    }
}

... where specific configuration is the location /optimized-download section.

Note

/optimized-download is not available for the client, i.e. users won’t be able to download files via /optimized-download/<filename>.

Warning

Make sure Nginx can read the files to download! Check permissions.

Global delegation, with XAccelRedirectMiddleware

If you want to delegate all file downloads to Nginx, then use django_downloadview.nginx.XAccelRedirectMiddleware.

Register it in your settings:

MIDDLEWARE_CLASSES = (
    # ...
    'django_downloadview.nginx.XAccelRedirectMiddleware',
    # ...
)

Setup the middleware:

NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT = MYAPP_STORAGE_LOCATION
NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL = '/optimized-download'

Optionally fine-tune the middleware. Default values are None, which means “use Nginx’s defaults”.

NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES = False  # Force no expiration.
NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING = False  # Force buffering off.
NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE = False  # Force limit rate off.

Local delegation, with x_accel_redirect decorator

If you want to delegate file downloads to Nginx on a per-view basis, then use django_downloadview.nginx.x_accel_redirect() decorator.

Adapt myapp/urls.py:

from django.conf.urls import url, url_patterns
from django.contrib.auth.decorators import login_required

from django_downloadview import ObjectDownloadView
+ from django_downloadview.nginx import x_accel_redirect

from myapp.models import Document


download = login_required(ObjectDownloadView.as_view(model=Document))
+ download = x_accel_redirect(download,
+                             media_root=settings.MY_APP_STORAGE_LOCATION,
+                             media_url='/optimized-download')

url_patterns = ('',
    url('^document/(?P<pk>[0-9]+/download/$', download, name='download'),
)

Common issues

Unknown charset "utf-8" to override

Add charset utf-8; in your nginx configuration file.

open() "path/to/something" failed (2: No such file or directory)

Check your settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT in Django configuration VS alias in nginx configuration: in a standard configuration, they should be equal.