This short post is not a step by step guide on how to deploy Django on a Linux machine. It tries to explain to readers what are the goals of a reasonable Django deployment and how they can be achieved so when reading other tutorials online readers can have a better picture of what’s actually going on.

Who speaks what

One way I like to think about Django deployment, or any kind of Web based service deployment, is by looking at who speaks what. In the context of Django, we know that Django itself speaks WSGI (or ASGI, for simplicity, I’ll use WSGI as the example) and the user’s browser speaks HTTP. So, apparently, there’s some translation needed. We need something that translates WSGI to HTTP. In many Django applications, the users would also like to access static files, such as images, style sheets, JavaScript files and files that are uploaded by users, these files usually lives in a file system, and they speak file system language, not WSGI, not HTTP, so there are translations to be done here too.

With this in mind, we can treat the server software as translators that can translates a language to another.


Because our Web services are eventually consumed by web browsers or other HTTP clients, the end goal of a successful deployment is to translate everything into HTTP and providing a single HTTP “access point” who knows how to pass things to other translators based on the requested path. As discussed above, there are really only two things needed to be translated: WSGI and files.


Things that translate WSGI to HTTP are called WSGI servers. They receive HTTP requests and translate them into WSGI environment variables and then calls your WSGI entry point with the WSGI environment variables, for Django, the WSGI entry point can be retrieved by calling django.core.wsgi.get_wsgi_application or is readily available in your projects’ file as application. There are plenty of WSGI servers to choose from, some common choices includes gunicorn and uWsgi. Django’s runserver command also starts a WSGI server. One thing to note though, is that uwsgi the protocol used by uWSGI is its own language, it is not HTTP and not WSGI, but uwsgi can be understood by nginx, a popular HTTP server, and you can configure uWSGI to speak HTTP directly. To simplify things, we can treat uWSGI and nginx together as a translator that translates WSGI to HTTP.

File (to WSGI) to HTTP

Now that WSGI to HTTP is handled, we have two path ahead of us for handling files on a file system.

The first one is to translate files to WSGI and because we already know how to translate WSGI to HTTP, the path from file to HTTP is also established.

The other one is to translate file directly to HTTP, this one is more efficient as you only need one translator instead of two. The problem is that (almost) all the WSGI servers can’t do this natively, meaning you can’t ask them to only translate paths that doesn’t starts with /static. To do this, you need a more generic HTTP server or proxy, such as nginx or caddy, you can ask them to serve files directly when the path starts with /static, otherwise, pass the HTTP request to your WSGI server. Note that it would be impossible to do this when we don’t have access to the HTTP server configuration, for example, when using Heroku.

Django’s built-in development server goes the first path, it turns all HTTP requests to WSGI and inside WSGI application if it sees the requested URL matches that of a static file, it then reads the file and returns its content. There are other libraries that does this better than Django’s built-in server, such as whitenoise. This approach is not completely unusable, especially when you have a CDN server that caches all static resources, so future requests only hits the CDN server not your Django applications.

Non Django-specific goals and tools

When doing a production deployment, it’s not enough to just Django running. We need users to visit our website though a domain name, so a DNS record that points to our server will be required and port 80 will need to be open on the server. For security, we would like to provide HTTPS accessibility too. To do this, we can use nginx with some plugin to automatically request SSL certificates from Let's Encrypt. Cloudflare is a popular CDN/DNS service provider that have built-in support for HTTPS and caching.

It is also desired to have some kind of process manager to run our WSGI servers instead of running it on the foreground in the shell. Process managers allows us to monitor the status of servers, auto start servers when machine reboots, scale servers to have more processes and other benefits. supervisor is a popular option in Django community as a simple process manager.

uWSGI Goodies

As mentioned above, uWSGI can be configured to speak HTTP directly. Turns out, it can also be configured to serve static files. So you really can do a Django deployment with only uWSGI. Here’s an example configuration:

http            =
static-map      = /static=/myproj/static
static-map      = /media=/myproj/media
chdir           = /myproj
module          = myproj.wsgi
home            = /myproj/venv  # this is the virtualenv path
master          = true
processes       = 10
vacuum          = true