Servers

Note

Deployment arcitectures vary widely depending on the needs and traffic of the site. The setup described below is minimally configured and works well for most instances.

We serve Django on Ubuntu Linux with a PostgreSQL database backend via gunicorn or uWSGI from behind an Nginx frontend proxy. For simplicity, we’ll only be discussing Gunicorn/Nginx here.

Nginx

Nginx makes for a great frontend server due to its speed, stability and low resource footprint. The typical Nginx configuration for a site looks like this:

# Gunicorn server
upstream django {
  server         domain.com:9000;
}

# Redirect all requests on the www subdomain to the root domain
server {
  listen      80;
  server_name www.domain.com;
  rewrite ^/(.*) http://domain.com/$1 permanent;
}

# Serve static files and redirect any other request to Gunicorn
server {
  listen       80;
  server_name  domain.com;
  root        /var/www/domain.com/;
  access_log  /var/log/nginx/domain.com.access.log;
  error_log  /var/log/nginx/domain.com.error.log;
  
  # Check if a file exists at /var/www/domain/ for the incoming request.
  # If it doesn't proxy to Gunicorn/Django.
  try_files $uri @django;
  
  # Setup named location for Django requests and handle proxy details
  location @django {
    proxy_pass         http://django;
    proxy_redirect     off;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
  }
}

What Does it Do?

The first block tells Nginx where to find the server hosting our Django site. The second block redirects any request coming in on www.domain.com to domain.com so each resource has only one canonical URL. The final section is the one that does all the work. It tells Nginx to check if a file matching the request exists in /var/www/domain.com. If it does, it serves that file, if it doesn’t, it proxies the request to the Django site.

SSL

Another benefit to running a frontend server is SSL termination. Rather than having two Django instances running for SSL and non-SSL access, we can have Nginx act as the gatekeeper redirecting all requests back to a single non-SSL WSGI instance listening on the localhost. Here’s what that would look like:

server {
  listen       67.207.128.83:443; #replace with your own ip address
  server_name  domain.com;
  root        /var/www/domain.com/;
  access_log  /var/log/nginx/domain.com.access.log;

  ssl on;
  ssl_certificate /etc/nginx/ssl/certs/domain.com.crt;
  ssl_certificate_key /etc/nginx/ssl/private/domain.com.key;
  ssl_prefer_server_ciphers       on;
  
  # Check if a file exists at /var/www/domain/ for the incoming request.
  # If it doesn't proxy to Gunicorn/Django.
  try_files $uri @django;
  
  # Setup named location for Django requests and handle proxy details
  location @django {
    proxy_pass         http://django;
    proxy_redirect     off;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Protocol ssl;
  }
  	    
}

You can include this code at the bottom of your non-SSL configuration file.

Gunicorn

Gunicorn is a lightweight WSGI server that can scale from small deploys to high-traffic sites. You can install it via pip install gunicorn. Since Nginx will be listening for HTTP(S) requests, you’ll need to bind Gunicorn to a different port. While you’re at it, you can tell it to only respond to the localhost. A simple gunicorn process might look like this:

$ gunicorn --workers=4 --bind=127.0.0.1:9000 my_project.wsgi:application

This spawns a gunicorn process with 4 workers listening on http://127.0.0.1:9000. If your project doesn’t already have a wsgi.py file, you’ll want to add one. See the Django WSGI docs or django-layout for an example.

Process Management

You want to be sure that gunicorn is always running and that it starts up automatically after a server reboot. If you are deploying to Ubuntu, upstart is probably the easiest way to get started. Here is a sample config:

# logs to /var/log/upstart/my_project.log

description "my_project"
start on startup
stop on shutdown

respawn

# start from virtualenv path
exec /opt/webapps/my_project/bin/gunicorn  -w 4 -b 127.0.0.1:9000 my_project.wsgi:application
setuid www-data

Save this file to /etc/init/gunicorn.conf and run sudo start gunicorn. For troubleshooting, your logs will be visible at /var/log/upstart/gunicorn.log.

Note

Supervisor is a pure Python option if you don’t have access to upstart.