Installing Django on mod_wsgi, in a virtualenv behind Nginx on Ubuntu

These instructions will help you walk through setting up Django in one of the preferred deployments: We'll use Nginx as a proxy to pass off requests to Django. One of the benefits here is that you can set up other sites on your server running on various technologies and nginx will just pass the requests to the correct handler. This way, you can have some static sites, maybe a couchdb instance, a rails app, a Django app etc, all on the same server, which should be fine for low traffic sites. Django will run on Apache under modwsgi and live inside a virtualenv so you can install libraries or upgrade your Python wihout messing with your system Python.

I'm using a Linode instance with Ubuntu 10, but the instructions should be helpful for any Linux. One last thing, I'm not an ops guy. If you see something wrong, please let me know.

Before beginning, I'd just go read through the docs on deploying Django on mod_wsgi and running mod_wsgi on virtualenv. Both are pretty good. If you have some experience configuring a server, they're all you need. If you don't, having them in your head will be handy as you work through the following and they'll be crucial during trouble shooting. Either way, let's get started..

First, install virtualenv with easy_install. Create a directory in /usr/local/ called pythonenv and change into it, so you're in:

1
/usr/local/pythonenv

Then, create a clear virtualenv by running the command:

1
virtualenv --no-site-packages BASELINE

Create a user and under that create a directory sites/ and cd into it, so your pwd is something like this:

1
/home/someuser/sites/

In this folder, create a new virtualenv; this is the virtualenv that you'll install Django into.

1
virtualenv --no-site-packages example.com.env

Now if you ls example.com.env you should see:

1
bin include site

Create two more directories, site and src.

1
mkdir example.com.env/site; mkdir example.com.env/src;

Checkout Django into the src directory

1
svn co http://code.djangoproject.com/svn/django/trunk/ example.com.env/src/django-trunk

Then add django to the environment's path. You can do this with a symbolic link, or a .pth file. Your choice. Here's the .pth file creation:

1
2
echo $PWD/example.com.env/src/django-trunk >
example.com.env/lib/python2.6/site-packages/django.pth

(Note, check your python version above). If you want, you can set up django-admin on your shell path. Complete details on this can be found on the Django site.

At this point you should be able to activate your virtualenv and import django.

1
2
3
source example.com.env/bin/activate
python
>>> import django

Now, create or move your Django application code into the site directory you created a few steps earlier. You'll need to create a wsgi file if you don't have one already and add it somewhere in this folder. For help creating the WSGI file, follow the docs on djangoproject.com, but skip the Apache configuration part at the beginning. Just make the wsgi file and add it to a directory in your Django project. You'll also have to add the following to the top:

1
2
import site
site.addsitedir('/home/someuser/sites/example.com.env/lib/python2.6/site-packages')

Change the directory above to match the site-packages directory in your new virtualenv.

I personally set up two versions of my Django settings under a common "configs" folder with separate subdirectories for wsgi and settings files which correspond to my development and production environments. This is a technique I think I got from Simon Willison, but you can keep it anywhere in your project. Most people create an "apache" directory and stick it there. Just make sure all the configurations coming up point to it. Now follow the instructions to set up Apache2. but don't follow the virtual host configuration. When you get there, instead install modwsgi:

1
2
3
apt-get update
apt-get upgrade
apt-get install python-setuptools libapache2-mod-wsgi

Then configure your virtual host with the following instructions. First, put the following in your ports.conf file.

1
NameVirtualHost *:8080

Or whatever port you want to run Django on. Remember Nginx will be listening on 80 and passing off requests to Apache on some other port.

Then go to your apache conf folder (probably /etc/apache2/) and inside the site-available folder, make a file called example.com or whatever your domain is. Inside, put something like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<VirtualHost *:8080>
    ServerAdmin webmaster@example.com
    ServerName  www.example.com
    ServerAlias example.com

    <Directory /home/someuser/example.com/site/configs/prod/ >
            Order deny,allow
            Allow from all
    </Directory>

    WSGIScriptAlias / /home/someuser/sites/example.com.env/site/configs/prod/apache.wsgi
    WSGIDaemonProcess  example.com user=www-data group=www-data threads=25
    WSGIProcessGroup example.com

    ErrorLog  /var/www/example.com/logs/error.log
    CustomLog /var/www/example.com/logs/access.log combined
</VirtualHost>

The important thing in this configuration is that the Directory directive points to the folder you put your Django wsgi file and the WSGIScriptAlias points directly to it. While you're at it, you can place your error logs whereever you want as well. The above might not be a great place for them. Make sure the directories exist.

Ok, now enable this site by running a2ensite

1
sudo a2ensite example.com

This creates symlink from your available sites to your enabled sites.

Also, edit your main apache2.conf file in /etc/apache2/ and at the bottom of the file add:

1
WSGIPythonHome /usr/local/pythonenv/BASELINE

Now you've installed a virtualenv with Django and Apache with mod_wsgi, so we're getting close.

If you haven't already, you’ll need to create a DNS entry in the Linode manager for your domain and point your domain to the Linode nameservers.

Edit your /etc/hosts file so it has a line like this.

1
127.0.0.1        localhost.localdomain        localhost

You can add your domain to this too. Add it as 127.0.0.1 or your external IP name.

Make up some name for your server and make it your machine's hostname. For our purposes I'm going to use 'paintbrush'.

1
2
echo "paintbrush" > /etc/hostname
hostname -F /etc/hostname

Now install Nginx using the linode instructions

Then edit your nginx config file, which is probably at:

1
/opt/nginx/conf/nginx.conf

Nginx has a straightforward configuration syntax you should be able to figure out just by reading the examples. Follow Linode's guide. But the following is what you want...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
server {
    listen       80;
    server_name  *.example.com;
    access_log   logs/example.com.access.log;
    error_log   logs/example.com.error.log;

    location / {
        proxy_pass http://example.com:8080;
        include /opt/nginx/conf/proxy.conf;
    }
}

Finally, you can add location configurations to the above to server your static media. Something like this should work, but again, refer to the Nginx documentation.

1
2
3
location /media/  {
   root    /home/someuser/example.com.env/site/static/
}

Now, retstart Apache and Nginx

1
2
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/nginx restart

Fire up a browser and go to your domain. Guess what? it didn't work. This is a complicated process and you're bound to mess something up. The key here is that you need to check your logs. Tail the nginx and apache error logs that you configured above and fix each problem until you have this working.

If you see import errors, that means your Python path isn't correct. Make sure you really understand it, fix it and don't forget to restart Apache when you're testing fixes.

2011-05-11