Django with nginx, mod_wsgi, and SSL

Avatar for vbabiy@howsthe.com

Django with nginx, mod_wsgi, and SSL

Published Sept. 20, 2009 by Vitaly Babiy

Howsthe.com has made an architectural change in our deployment stack. We were using mod_wsgi and Apache to serve the static content, which was not working as well as planned. We moved to using nginx as a front end proxy and a static content server. It also acts as a gateway for the SSL connection so we don't have to run different mod_wsgi instances for HTTP connections and for HTTPS connections. Below we explain how we configured this setup.

Prerequisites:

We'll be using Ubuntu 9.04 server, the following packages need to be installed:

  • apache2
  • libapache2-mod-wsgi
  • nginx
  • subversion ( Only if you are going to install Django from SVN )

This doesn't include database installation, for this example we will not be using a database. If your application uses a database, install the correct python drivers and make sure your settings.py file is configured correctly.

Once these packages are installed, then install Django. We will not be going over this here due to expediency. You can find the directions at http://docs.djangoproject.com/en/dev/intro/install/.

Creating A Simple Django Project:

We need to create a simple Django project to demonstrate that the nginx and mod_wsgi are working together, and to test the SSL gateway through ngnix.

django-admin.py startproject simple_ssl_project
cd simple_ssl_prject
python manage.py startapp ssl_test

Now that we have our project and a Django application in place, we can create the view and url to test for the SSL support.

# ssl_test/views.py:
from django.http import HttpResponse

def using_ssl(request):
    """ Simple View that will return true if it is using HTTPS """
    if request.is_secure():
        msg = "Yes we are secure"
    else:
        msg = "We are not running in secure mode"
    return HttpResponse(msg)

And now the url.py.

# url.py (Root Url Conf):
from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^', 'ssl_test.views.using_ssl')
)

We have a very simple application setup, we first have to get it to work with mod_wsgi. We will need to create a wsgi handler, I usually place this file in the root of the project and name the file wsgi_handler.py.

# wsgi_handler.py: 
import sys
import os

sys.path.append('/home/vbabiy/')
sys.path.append('/home/vbabiy/simple_ssl_project/')
os.environ['DJANGO_SETTINGS_MODULE'] = 'simple_ssl_project.settings'

import django.core.handlers.wsgi

application = django.core.handlers.wsgi.WSGIHandler()

We need to configure apache and create a virtual host.

# /etc/apache2/sites-available/example.com
<VirtualHost *>

  ServerName www.example.com
  ServerAlias *example.com

  # In production you will probly want to setup your logging config

  WSGIDaemonProcess simple_test user=vbabiy group=vbabiy display-name=%{GROUP} maximum-requests=10000
  WSGIProcessGroup simple_test

  WSGIScriptAlias / /home/vbabiy/simple_ssl_project/wsgi_handler.py

</VirtualHost>

For the user and group on the WSGIDaemonProcess, you will need to change them to be valid for you setup. This should be the user and group you would like the process to run as. You now need to enable the site:

sudo a2ensite example.com # Vhost file name

You will need to change which port Apache is going to be listening on, by default this is set to port 80 which needs to be set to a different port. For this example we will be using 8080. This can be done in the /etc/apahce2/ports.conf file. We need to modify this because when we are setting up ngnix as the front end proxy it will be listening on port 80.

# /etc/apache2/ports.conf
Listen 8080

<IfModule mod_ssl.c>
    # SSL name based virtual hosts are not yet supported, therefore no
    # NameVirtualHost statement here
    Listen 443
</IfModule>

And now you can restart apache:

sudo /etc/init.d/apache2 restart

Now that apache is running on port 8080, we need to setup nginx to handle requests on port 80. First create a proxy configuration file in the /etc/ngnix directory, name it proxy.conf content of the file is below:

# /etc/ngnix/proxy.conf
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;
client_max_body_size        10m;
client_body_buffer_size     128k;
proxy_connect_timeout       90;
proxy_send_timeout          90;
proxy_read_timeout          90;
proxy_buffer_size           4k;
proxy_buffers               4 32k;
proxy_busy_buffers_size     64k;
proxy_temp_file_write_size  64k;

Create a server configuration for ngnix, the file needs to be in /etc/nginx/site-available directory with the similar content to:

# /etc/nginx/site-available/example.com
upstream django {
    server         127.0.0.1:8080;
}

# This can be used to redirect example.com to www.example.com
#server {
    #server_name  example.com;
    #rewrite ^(.*) http://www.example.com$1 permanent;
#}

server {
    listen   80;
    server_name  example.com;

    location / {
        proxy_pass  http://django; 
        # This will proxy this request to upstream of django which is defined above.
        include     /etc/nginx/proxy.conf;
    }
    # Link your media to server static content
    location /media {
        root    /home/vbabiy/simple_ssl_project/media/;
        # This would be set to whatever is in your setting.MEDIA_ROOT.
    }
}

Static Media:

In the ngnix configuration we set the location of the static content in the following code snippet (this snippet is from the above code).

location /media {
    root    /home/vbabiy/simple_ssl_project/media/;
}

This code is self explanatory, if you have more than one static media source for example, your admin media, you can add more than one of these definitions per server configuration.

Now that we have added the site to the site-available directory, we need to create a simlink to the sites-enabled directory.

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com

In order for these configurations to take place, you need to restart ngnix. If you get no errors you should be able to go to http://localhost and see our Django application running you should see We are not running in secure mode.

Debugging (I see errors): If you are seeing errors on the command line or thourgh the web browser, you should check the error log for more details. By default it is located at /var/log/ngnix/error.log.

Our project is now running using nignx as a front end proxy and mod_wsgi in the back end. If this is all your application requires you're done. Otherwise if you need to setup SSL support in nignx follow on below.

Adding SSL Support: In order to get SSL support we will need to make a few changes to our wsgi handler and add the SSL support to nginx. We will start off by adding it nignx, but before we do that you will need to have a SSL certification or you can create your own signed certificate. It is not recommended to use in production, but if you are using this in a staging or testing enviorment it is fine. Ubuntu's wiki has a great step by step of how to create one: http://help.ubuntu.com/8.04/serverguide/C/certificates-and-security.html.

We will need to append this server configuration to the nginx configuration:

server {
    listen 443;
    server_name  example.com;

    ssl on;
    ssl_certificate /etc/nginx/ssl/certs/example.com.crt; # Specify the location of the cert
    ssl_certificate_key /etc/nginx/ssl/private/example.com.key; # Specify the location of the private key
    ssl_prefer_server_ciphers    on;

    include    /etc/nginx/proxy.conf;
    proxy_set_header    X-Forwarded-Protocol https; # Forward in the request to django

    location / {
        proxy_pass    http://django;
    }
    location /media {
        root    /home/vbabiy/simple_ssl_project/media;
        # This would be set to whatever is in your setting.MEDIA_ROOT.
    }
}

After adding the certificate location to the configuration you need to restart nginx, to reload the new configuration

If you try to access django application with https://localhost you will get a warning from the browser that you are using an invalid security certificate, because it is a self signed certificate. You may just ignore this error, and proceed. Once you see the output of the Django application you will notice that the output is still We are not running in secure mode, this is caused by using nginx as a SSL gateway for the application, which is easy to fix. We will need to modify the wsgi_handler to add some logic on how to pass the HTTPS header.

Modified wsgi_handler:

import sys
import os

sys.path.append('/home/vbabiy/')
sys.path.append('/home/vbabiy/simple_ssl_project/')
os.environ['DJANGO_SETTINGS_MODULE'] = 'simple_ssl_project.settings'

import django.core.handlers.wsgi

_application = django.core.handlers.wsgi.WSGIHandler()

def application(environ, start_response):
    # trick django into thinking proxied traffic is coming in via HTTPS
    # HTTP_X_FORWARDED_SSL is used on WebFaction
    if environ.get("HTTP_X_FORWARDED_PROTOCOL") == "https" or \
       environ.get("HTTP_X_FORWARDED_SSL") == "on":
        environ["wsgi.url_scheme"] = "https"
    return _application(environ, start_response)

You should see the output of the ssl_test application in your browser as Yes we are secure. Now nginx is serving your static content and acting as a SSL gateway for all apache mod_wsgi instances in the background.

If you have any suggestions, question, or comments with this deployment stack please leave it in the comments.

Written By Vitaly Babiy

Avatar for vbabiy@howsthe.com

Vitaly Babiy is the creator of Howsthe.com (Yes, you can contact him about the service). He is a software engineer at heart, loves working with great technologies like Django and Jquery. Vitaly spends most of his days in python and loves it. Another passion of Vitaly's is learning the business side of things, one of the reason why he started Howsthe.com monitoring service. You can follow him on Twitter

blog comments powered by Disqus

A blog about development, marketing, and design.