Apache + mod_wsgi + Django + lighttpd

I’ve written how to configure Apache, mod_python and Django and how to put lighttpd behind Apache.

Recently I decided to host my most visited website on a different VPS provider[1], and started a quest to update my knowledge about Django deployment. I did things differently this time, using mod_wsgi (the recommended way of deploying Apache and Django), and configuring Apache behind lighttpd for dynamic content (in other words, lighttpd will serve static media).

I did everything below in the last couple days, and did not wrote things as I was doing it, because it involved a lot of experimentation (trial and error) for me. As such, I am writing this article based on memory and checking my config files. If you encounter any problems, please leave a comment and I will clarify any omissions.

Here’s how to do it:

Install the usual suspects

I choose Ubuntu as my Linux distro, and installation of anything is a breeze on it. sudo apt-get install package-name. This part is well covered around the web, so I won’t comment in details how it’s done. Sufficient to say, some of the packages I’ve installed were apache2, libapache2-mod-wsgi, and lighttpd.

Configure Apache and mod_wsgi to load your project

Since lighttpd will act as the primary server for my domain, I decided to move Apache to port 81:

sudo vi /etc/apache2/ports.conf

Overwrite existing ip:port lines with these:

Listen 81

Where you put your own Python modules on newer Ubuntu installations has changed to /usr/local/lib/python2.6/dist-packages/. Therefore, I’ve uploaded Django, my project and other necessary modules (which weren’t installed by apt-get) to this directory, leaving me with the following structure:


The mod_wsgi documentation has an excellent article on Django integration, but it’s fairly length. You should read it anyway, since there are lots of options that you might want to use. Here’s a cheatsheet:

Create a document root for your domain name:

sudo mkdir /var/www/example.com

Create the file which will be loaded by mod_wsgi with your project configuration:

sudo mkdir /usr/local/lib/python2.6/dist-packages/project_name/apache/
sudo vi /usr/local/lib/python2.6/dist-packages/project_name/apache/django.wsgi

With these contents:

import sys
import os

os.environ[‘DJANGO_SETTINGS_MODULE’] = ‘project_name.settings’

import django.core.handlers.wsgi

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

Create a domain configuration file for Apache:

sudo vi /etc/apache2/sites-available/example.com

With these contents:

ServerName example.com
ServerAdmin [email protected]

DocumentRoot /var/www/example.com

Alias /media/ /usr/local/lib/python2.6/dist-packages/django/contrib/admin/media/
<Directory /usr/local/lib/python2.6/dist-packages/django/contrib/admin/media>
Options -Indexes
Order deny,allow
Allow from all

Alias /project_media_dir/ /usr/local/lib/python2.6/dist-packages/project_name/templates/project_media_dir/
<Directory /usr/local/lib/python2.6/dist-packages/project_name/templates/project_media_dir>
Options -Indexes
Order deny,allow
Allow from all

WSGIScriptAlias / /usr/local/lib/python2.6/dist-packages/project_name/apache/django.wsgi
WSGIDaemonProcess example.com
WSGIProcessGroup example.com

<Directory /usr/local/lib/python2.6/dist-packages/project_name/apache>
Order deny,allow
Allow from all

Activate it:

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

Configure lighttpd to proxy non-static media requests to Apache

I used MySQL Performance Blog’s “Lighttpd as reverse proxy” article as a basis for my own configuration. Therefore, we’ll have an url http://example.com/server-status, which will require authentication, enabling us to see Apache’s server status.

Create a directory for error logs:

sudo mkdir /var/log/lighttpd/example.com

Create a domain file configuration for lighttpd:

sudo vi /etc/lighttpd/conf-available/20-example.com.conf

With these contents:

server.modules += ( “mod_auth”,

$HTTP[“host”] =~ “(^|\.)example\.com$” {
$HTTP[“url”] !~ “\.(js|css|gif|jpg|png|ico|txt|swf|html|htm)$” {
proxy.server = ( “” => (
( “host” => “”, “port” => 81 )

server.document-root = “/var/www/example.com/”
server.errorlog = “/var/log/lighttpd/example.com/error.log”
dir-listing.activate = “disable”

auth.backend = “htpasswd”
auth.backend.htpasswd.userfile = “/var/www/.htpasswd”
auth.require = ( “/server-status” => (
“method” => “basic”,
“realm” => “status”,
“require” => “valid-user”

There are lines worthy of mention in the configuration above:

$HTTP[“host”] =~ “(^|\.)example\.com$” {

This will wrap the directives inside to only apply for example.com requests.

$HTTP[“url”] !~ “\.(js|css|gif|jpg|png|ico|txt|swf|html|htm)$” {
proxy.server = ( “” => (
( “host” => “”, “port” => 81 )

These will send any requests for documents not ending in the specified extensions to ip, port 81, where Apache lives. Essentially, everything which is static content (or more accurately, specified by the | separated regular expression), will be served by lighttpd.

cd /etc/lighttpd/conf-enabled/
sudo ln -s ../conf-available/example.com.conf

Tell the filesystem where you project and Django’s admin static content are located:

sudo ln -s /usr/local/lib/python2.6/dist-packages/django/contrib/admin/media/ /var/www/example.com/media
sudo ln -s /usr/local/lib/python2.6/dist-packages/project_name/templates/project_media_dir/ /var/www/example.com/project_media_dir/

Finally, restart everything so the newest configuration can be applied

sudo /etc/init.d/apache2 restart
sudo /etc/init.d/lighttpd restart

So, what actually happens?

When a visitor goes to your website (example.com), the request will hit lighttpd first. If the document path does not end with a string in our list of static content extensions, the request will be proxied to Apache on port 81, otherwise lighttpd will serve itself.

And that’s it, if my memory is correct. Did I miss anything? Comment at will.

[1] Linode, if you’re curious. Mainly because bandwidth is cheaper. If you’re looking for a Linode referral, Linode discount code or Linode promotion code, sign up using this link to credit me as referral. Thanks 🙂





3 responses to “Apache + mod_wsgi + Django + lighttpd”

  1. Lukas Avatar

    Awesome! This helped me a lot. Just one little question: I want SSL – Do I need to configure Lighttpd for SSL encryption, or both, Lighttpd and Apache? Tank you! (No idea how this proxied-requests-thing works :/ )

  2. Ksk Avatar

    Adorei o BLOG! Muito bom!Hospedagem 30 dias grátis!

  3. Jurgen Welschen Avatar
    Jurgen Welschen

    Love it! worked first try. Thanks a lot.