Nginx, Gunicorn and Django   Oct 26, 2014

Nginx is becoming more popular all the time as it's ease of configuration and low memory footprint make it a better choice than Apache for many websites. This post discusses the basics of serving a Django application behind nginx using Gunicorn.

Nginx serves static files but Django is a dynamic server-side web framework, so an additional server needs to serve the dynamic content while nginx will serve the static content. There are a few available but for this post I will discuss Gunicorn.

The block below shows a configuration file for nginx which uses gunicorn as an upstream proxy server to serve the Django app content. This uses a unix socket file instead of a TCP port and simply removes the TCP overhead. An unnecessary optimisation in most cases, but I did it here for demonstration purposes. The server block simply contains the normal nginx configuration. This includes the port to listen on, where to write error and access logs, which server name to map this configuration to, a couple of aliases to map requests for static files to filesystem directories. Finally, there's the section mapping all other requests to the Django app. This sets the appropriate headers to pass the request to the proxy server set up at the beginning of the file.

Nginx Configuration

upstream mysite.com_app_server {
  # fail_timeout=0 means we always retry an upstream even if it failed
  # to return a good HTTP response (in case the Gunicorn master nukes a
  # single worker for timing out).

  server unix:/opt/ fail_timeout=0;

server {
    listen 80;
    server_tokens off;
    access_log /opt/;
    error_log /opt/;
    return 301 https://$server_name$request_uri;
server {
    listen 443 ssl;
    server_tokens off;
    ssl_certificate /etc/nginx/ssl/bundle.crt;
    ssl_certificate_key /etc/nginx/ssl/;
    access_log /opt/mysite/logs/nginx-access.log;
    error_log /opt/mysite/logs/nginx-error.log;

    location /static/ {
        alias /opt/;
        if ($uri ~* "\.(?:js|css|png|jp?g|gif|ico|woff)$") {
            expires 7d;

    location /robots.txt {
        alias /opt/mysite/blog/blog/static/robots.txt;
        expires -1;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        if (!-f $request_filename) {
            proxy_pass http://mysite.com_app_server;

If the url starts with /static/ or if it's for /robots.txt then it will serve these static files itself and set the appropriate cache headers, otherwise it will pass the request to the Django application via Gunicorn.

Gunicorn configuration

The block below is \opt\\bin\gunicorn_start and is used to start the Gunicorn server to serve the Django application. In the nginx config above, the first block points all requests that are not for static files at an upstream proxy server called mysite.com_app_server and this points at the UNIX socket that Gunicorn is listening on.

NAME=""                             # Name of the application
BASEDIR=/opt/                       # Project directory
DJANGODIR=$BASEDIR/blog                       # Django project directory
SOCKFILE=$BASEDIR/run/gunicorn.sock           # we will communicate using this unix socket
USER=username                                 # the user to run as
GROUP=groupname                               # the group to run as
NUM_WORKERS=3                                 # how many worker processes should Gunicorn spawn
#DJANGO_SETTINGS_MODULE=blog.settings_dev     # settings file with Debug on and other dev settings
DJANGO_SETTINGS_MODULE=blog.settings_prod     # settings file with Debug off and other prod settings
DJANGO_WSGI_MODULE=blog.wsgi                  # WSGI module name
echo "Starting $NAME as `whoami`"
# Activate the virtual environment
source ../bin/activate
# Create the run directory if it doesn't exist
test -d $RUNDIR || mkdir -p $RUNDIR
# Start your Django Unicorn
# Programs meant to be run under supervisor should 
#  not daemonize themselves (do not use --daemon)
exec ../bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --log-level=debug \
  --bind=unix:$SOCKFILE \
  --access-logfile $BASEDIR/logs/gunicorn-access.log \
  --error-logfile $BASEDIR/logs/gunicorn-error.log \
  --pid $BASEDIR/run/

Nginx accepts incoming requests and decides whether to serve them itself or pass them to the proxy server (Gunicorn).

Tags for this post: