A Django deployment guide for Ubuntu

Ignite your Django projects with nginx, Apache and mod_wsgi

January 2012 update: I use and recommend gunicorn and Nginx for Django projects now.

There's a time when every Django developer has to think about deployment scenarios, when I first did it last year I thought that a setup involving Lighty, Apache, mod_proxy and mod_python was a good choice but my first approach was not the best. I put Apache as the front server, handling requests for Django generated pages and passing, via mod_proxy, requests for static content to Lighty on the back. A setup where Apache had to work even for files that wasn't supposed to serve was a very bad idea.

After many helpful comments and some more reading I realized that it was better having the server for static content on the front and Apache, which still talks to Django, on the back.

I replaced Lighty with nginx, which according to many seems to be more stable, and opted for mod_wsgi instead of mod_python to make Apache talk to Django. mod_wsgi has a lower memory overhead and it's suitable for high performance sites. There's no need for mod_proxy on Apache anymore as nginx is the one in charge of the proxy work now.

This is an easy to follow and very focused guide for developers who know how to handle their servers so I won't consider security issues, memcached, Django installation, databases or basic GNU/Linux, Apache and DNS settings. Of course all of those subjects are important and you should take care of them.

The plan

This guide includes all the steps needed to:

  • Setup a domain for your Django project.
  • Create a simple directory layout for Django sites.
  • Configure Apache with mod_wsgi for Django.
  • Configure nginx.
  • Serve Django admin media files.
  • Turn on the heat and show your greatest and latest Django stuff to the world.

After following all the steps you will have a Django site running with nginx on the front and Apache on the back. nginx will manage all static content and will pass Django requests to Apache and mod_wsgi.

I have tested on three Ubuntu servers (two running 8.10, Intrepid Ibex, and one 7.10, Gutsy Gibbon) but everything should be pretty similar in other GNU/Linux distributions.

Shall we start?

Setup a domain for your Django project

Many of us run more than one website in one server, I have several with Drupal and a few with Django living in one box, so let's consider a two domains scenario: cataybea.com will host the Django project and example.com anything else. Most of the work will focus in cataybea.com, I just mention example.com to give you some context. Obviously you should replace all the domain names in this guide with yours.

We'll use a couple of private IP addresses for now and will replace them with public ones and setup in a DNS server when going live. For this guide I'll just add to /etc/hosts, which is what I usually do in my development environment:

192.168.0.180 cataybea.com
192.168.0.192 example.com

I'm using 192.168.0.180 and two ports, 80 and 8080, for serving the Django site at cataybea.com. The default port 80 will be used by nginx to serve static content and Django will look for files under the /media directory. Port 8080 will be used by Apache to handle Django requests.

Create a simple directory layout for Django sites

Directory layouts depend on personal preferences, I currently use a /home/alexis/djcode directory for all my Django coding and the new project will be called cataybea; hence, we have to run:

mkdir /home/alexis/djcode
cd /home/alexis/djcode/
django-admin.py startproject cataybea

Now let's create some additional directories:

mkdir /home/alexis/djcode/cataybea/apache
mkdir /home/alexis/djcode/cataybea/logs
mkdir /home/alexis/djcode/cataybea/media

What are they for? apache will contain a Python script to setup mod_wsgi, logs will store Apache and nginx logs and media is the directory nginx will use to serve static files for our Django project.

You can later manage the whole /home/alexis/djcode/cataybea directory with your favorite version control tool.

Take note of the correct paths for using later in the configuration files.

Configure Apache with mod_wsgi for Django

I assume you already have Apache working correctly and just need to tweak a little for our Django setup. As we'll be running two web servers at once we must make sure that IP addresses and ports won't conflict. Let's edit /etc/apache2/ports.conf:

Listen 192.168.0.192:80
Listen 192.168.0.180:8080
#Listen 80

I have specified IP addresses and ports to listen. Notice I commented the default Listen 80 as it means Apache would listen to that port in all IP addresses. If you want to use SSL take care of the 443 port in the same way.

If you are using virtual hosts in Apache confirm it's listening to the correct IP addresses and ports, I have this in /etc/apache2/sites-enabled/example.com:

NameVirtualHost 192.168.0.192

Restart Apache and make sure your Apache sites work normally. I'm obsessed with testing at every step in the way and I suggest you are too.

sudo /etc/init.d/apache2 restart

Now it's time to add mod_wsgi to Apache. The latest versions of Ubuntu have it in the repository:

sudo apt-get install libapache2-mod-wsgi

For older versions of Ubuntu, such as Gutsy, you need to find the .deb file and use something like dpkg -i to install.

Now it's time to create the Apache configuration file for cataybea.com in /etc/apache2/sites-available/cataybea.com:

<VirtualHost 192.168.0.180:8080>
ServerAdmin alexis@ventanazul.com
ServerName www.cataybea.com
ServerAlias cataybea.com
<Directory /home/alexis/djcode/cataybea/apache/>
Order deny,allow
Allow from all
</Directory>
LogLevel warn
ErrorLog /home/alexis/djcode/cataybea/logs/apache_error.log
CustomLog /home/alexis/djcode/cataybea/logs/apache_access.log combined
WSGIDaemonProcess cataybea.com user=www-data group=www-data threads=25
WSGIProcessGroup cataybea.com
WSGIScriptAlias / /home/alexis/djcode/cataybea/apache/cataybea.wsgi
</VirtualHost>

Notice the IP address and port: 192.168.0.180:8080. This site won't be accessed directly but via proxy from nginx. We'll set that up in the next section.

Now we need a Python script to configure our Django project to use mod_wsgi, create /home/alexis/djcode/cataybea/apache/cataybea.wsgi and put the following code inside:

import os, sys
apache_configuration= os.path.dirname(__file__)
project = os.path.dirname(apache_configuration)
workspace = os.path.dirname(project)
sys.path.append(workspace)
sys.path.append('/usr/lib/python2.5/site-packages/django/')
sys.path.append('/home/alexis/djcode/cataybea')
os.environ['DJANGO_SETTINGS_MODULE'] = 'cataybea.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Don't restart Apache yet as we need to complete a few more steps.

Configure nginx

nginx will do two things: serve static content from http://cataybea.com/media and pass all other requests to Apache. Let's install it first:

sudo apt-get install nginx

Now remove the nginx default site:

sudo rm /etc/nginx/sites-enabled/default

Apache and nginx should be using the same user, for Ubuntu this is www-data, and your /etc/nginx/nginx.conf should look like this:

user www-data;
worker_processes 2;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
tcp_nodelay on;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

worker_processes can be set to the number of cores in your server, in my case that's just 2.

Configure a the cataybea.com site on nginx by creating /etc/nginx/sites-available/cataybea.com:

server {
listen 192.168.0.180:80;
server_name www.cataybea.com cataybea.com;
access_log /home/alexis/djcode/cataybea/logs/nginx_access.log;
error_log /home/alexis/djcode/cataybea/logs/nginx_error.log;
location / {
proxy_pass http://192.168.0.180:8080;
include /etc/nginx/proxy.conf;
}
location /media/ {
root /home/alexis/djcode/cataybea/;
}
}

then running:

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

and finally creating the file that will take care of proxying Django requests to Apache, we'll call it /etc/nginx/proxy.conf and put this inside:

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;

Notice $host, $remote_addr and $proxy_add_x_forwarded_for are variables that will be handled by nginx, you don't need to change anything else in proxy.conf for a basic setup. When I started playing with nginx I thought these had to be replaced by me, I was wrong.

Finally restart nginx:

sudo /etc/init.d/nginx restart

and Apache:

sudo /etc/init.d/apache2 restart

That should be it! If you visit http://cataybea.com you should see some Django content (could be the welcome page, some of your views or a 404 page depending on your project status). To try the nginx media server create a test.htm file with some dummy content in /home/alexis/djcode/cataybea/media and visit http://cataybea.com/media/test.htm.

Serve Django admin media files (optional)

If you will use the Django admin application edit /home/alexis/djcode/cataybea/settings.py, make sure your database settings are correct, add 'django.contrib.admin' to the INSTALLED_APPS tuple and change the ADMIN_MEDIA_PREFIX like this:

ADMIN_MEDIA_PREFIX = '/media/admin/'

Let's enable the admin url by editing /home/alexis/djcode/cataybea/urls.py, uncommenting these two lines:

from django.contrib import admin
admin.autodiscover()

and adding this to urlpatterns:

(r'^admin/', include(admin.site.urls)),

Now let's create a symbolic link to the media files for the Django's administration section:

cd /home/alexis/djcode/cataybea/media
ln -s /usr/lib/python2.5/site-packages/django/contrib/admin/media/ admin

And finally let's create the administration user and the required tables in the database:

cd /home/alexis/djcode/cataybea/media
python manage.py syncdb

As with every other modifications in your Django code you will have to reload Apache for changes to take effect:

sudo /etc/init.d/apache2 reload

Visit http://cataybea.com/admin/,including the trailing slash, and you should be in business.

Show your Django projects to the world

The nginx and Apache setup I've just described is pretty easy to implement and seems to be the preferred method among many Django developers. I'm using it for a new project I'm working at, you may have already guessed this, cataybea.com. The site will be an online diary (ok, ok, a blog) where my wife and I will share some stories and tips about raising our two daughters, it will be fun.

Now that you have everything ready for deploying your next project I suggest you keep working with Django's development server until you have everything ready to move to Apache and nginx. The development server refreshes automatically after every change in your code so you can avoid frequent Apache reloads.

Thanks to Meppum for the very helpful Django and Ubuntu setup guide that inspired this article.

Let me know if you have any suggestions for improving the guide and how it works for you. See you soon and happy Django coding.

Join the conversation

I'm bookmarking this post. I

I'm bookmarking this post.
I know I'll need it later.
Thank you.

I also like to use NginX as

I also like to use NginX as the front end server. it's fast, light, and easy. But on the back end, I'm using FastCGI (flup). Does Apache/mod_wsgi offer any advantage over FastCGI as a backend?

Why do you have Apache in the

Why do you have Apache in the mix at all? Nginx has a WSGI module, right? I'm not criticizing your setup, I'm trying to understand it.

This is awesome. Thanks for

This is awesome. Thanks for putting this up. I was about to start looking into setting up nginx with my django app and now I'll only have to look in one place.

Hey Jason. Well, I use Apache

Hey Jason. Well, I use Apache for a few other sites, most of them running Drupal, and am quite familiar with it so I didn't think much about other options.

It would be interesting testing an all Nginx setup for Django projects, I may give it a try one of these days and will let you know.

While nginx does have a WSGI

While nginx does have a WSGI module, nginx runs as a single-threaded server and so it's not clear how scalable an nginx-only solution would be. Apache is a bit heavy for static media serving, so a combined nginx-for-static-media + Apache-for-dynamic-content seems about right.

The best Django host (Webfaction) offers this combination on their newer servers.

Maybe limiting the access to

Maybe limiting the access to port 8080 with iptables or similar could be helpful. Currently everyone can access the apache directly via http://cataybea.com:8080/.

You're right Pascal, I tried

You're right Pascal, I tried to focus just in the web servers configuration and the Django part of the setup, of course there are security and a few other issues to take care of.

Thanks for the suggestion.

Wonderful guide, but I

Wonderful guide, but I misread how you were using the apache folder. Perhaps a quick directory dump would clear this up for others.

You just need the apache

You just need the apache directory to store one file: cataybea.wsgi

That's a Python script used to configure your Django project to use mod_wsgi.

In my example the full path is home/alexis/djcode/cataybea/apache/cataybea.wsgi

I put Apache as the front

I put Apache as the front server, handling requests for Django generated pages and passing, via mod_proxy, requests for static content to Lighty on the back. A setup where Apache had to work even for files that wasn't supposed to serve was a very bad idea.

There is nothing wrong with the nginx pick. However, the proxy service runs somewhere anyway. It doesn't matter if that is nginx or Apache. I would assume that mod_python would beat mod_wsgi quite handily just as mod_php beats the fastcgi version. There is no way multiplexing a socket can come close to a bound memory location when it comes to writing to scoreboard. The problem is expensive language processes waiting on browser connections. The proxy removes that issue because the proxy "baby sits" of the connection once it receives the result, just as it did for lighttpd, and the language process can go on to the next request. With either this, or the way you showed with mod_proxy, the language processing modules won't need to handle static content anymore, and you aren't tying up smart processes to serve static content. However with this method, since the proxy is not running in Apache, how do you handle .htaccess and mod_rewrite? Those two are basic to many web apps. Then there is the additional vhost file maintenance.

While nginx does have a WSGI module, nginx runs as a single-threaded server and so it's not clear how scalable an nginx-only solution would be. Apache is a bit heavy for static media serving, so a combined nginx-for-static-media + Apache-for-dynamic-content seems about right.

That man understands. The performance comes from taking away static content from Apache language interpreter, and proxying it to a stripped down web server so that the language processes don't get tied up chasing static content or feeding content to browsers. When servers like Lighttpd and Nginx use FastCGI, their hope is that the efficiency gains more than offset the loses incurred by FastCGI vs an SO. The reason that can happen is because for every dynamic element there may be 10 to 20 static ones. But why give up the speed? There is only one justification, and that is if you want to use a threaded server with non-threadsafe scripts. But that reasoning isn't valid with a proxy because you wouldn't have KeepAlive on anyway because the language interpreter process is not maintaining the connection with the browser anymore, the proxy is. ?NIX has no performance issues with either threads or processes, so now we're down to Windows. Nginx, a fork of apache 1.3 with the multi-processing removed in favor of an event loop, is not a technological marvel. Event loops are great for a large number of low activity operations such as feeding static content from OS cache to a proxy or browser. The IO model with threads or processes is what you need when scheduling processes under load and when they are waiting for information from a database. When there is little load, you can't tell the difference. With the level of parallelism achievable in hardware rapidly going up, the event loop will be ceding more and more ground to the IO model. FastCGI simply gets the light weight servers off the hook for something they cannot do, or cannot do well.

Personally, I'd do mod_proxy + nginx. nginx may be slightly faster if it also does the reverse proxy, but it's going to cost you. In a shared hosting environment, software and customers won't let you get away with not having .htaccess, and apache mod_rewrite, a lot of software also won't, and I wouldn't want to lose the Apache speed and reliability for dynamic content. Moreover, there is third party software that I would want to run as an admin that requires mod_proxy.

"Edit your settings.py file

"Edit your settings.py file to include django.middleware.http.SetRemoteAddrFromForwardedFor in MIDDLEWARE_CLASSES. This allows your Django application to use the real IP address of the client instead of 127.0.0.1 from your nginx proxy. It sets Django's request.META['REMOTE_ADDR'] to be request.META['HTTP_X_FORWARDED_FOR'] which we set above in nginx's proxy.conf."

Source: http://www.saltycrane.com/blog/2009/04/notes-using-nginx-mod_python-and-...

Great post! It works like a

Great post! It works like a charm and I have only one question :) (Perhaps it has nothing to do with this at all!?)

I'm not that good on this subject, thats why I read your post, so please bare with me.

I have my domain.com, and when I go to http://www.domain.com it works but when i type in http://domain.com it redirects to the .wsgi file in the apache-folder. I tried creating a .htaccess and put some rewrite stuff in it but it doesn't work. I put the .htaccess file in my project directory so please tell me if I'm placing it wrong.

This is what I wrote in the .htaccess file:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^domain.com
RewriteRule ^(.*)$ http://www.domain.com/$1 [R=permanent,L]

I hope someone can put me in the right direction?!

Thanks alot

Great Job! I spent 5 hour

Great Job!

I spent 5 hour studing your work and my django site was fired up.

anyway I had some trouble with /etc/apache2/ports.conf:

Listen 192.168.0.192:80
Listen 192.168.0.180:8080

Listen 80

I replace it with the following:
Listen 127.0.1.1:80
Listen 127.0.0.1:80
Listen 192.168.1.111:8080

the strangeness is that 192.168.1.111 is the static ip of my desktop; if I replace it with other private IP, it doesn't work

any suggestion will be appreciated.

Thanks for the great

Thanks for the great tutorial. Out of all the one's that I've tried, this was the only one to work perfectly.

I'm new to ubuntu and django

I'm new to ubuntu and django but want to use these programs.
I am getting mixed up from the start. You say make folders
then cd to the folder (got that) but where does this file "django-admin.py startproject cataybea" come from?
Is he saying I should touch,copy or move the file?
And if so, where do I find it. There's something simple I'm missing here right?

Damm Newbe's!! :)

I am at the end of this

I am at the end of this tutorial and I did this step
cd /home/alexis/djcode/cataybea/media
python manage.py syncdb

but the problem is manage.py file is not in the media directory it is in the cataybea directory.

Please send me a mail with solution of above problem.

Keep your comments relevant, written in good English and don't spam. Let's create useful and valuable discussions. Markdown is welcome.

Add your comment