How to configure nginx and uWSGI to serve Django projects

This time a post about Python...lately I was dealing with a Django based project. One of the tasks in the project was to deploy the application using nginx and uWSGI. To document the process and to help others I decided to post a short explanation on how to configure nginx, uWSGI and Django to work together. Unlike most articles I found over the internet I'm not just laying down the final configuration but I'm trying to explain what happens in each step – hopefully it will be useful to others.

nginx

I'm starting with nginx installation , since nginx development team provides a yum repository and rpm distribution this step is pretty easy, however it requires configuration of the nginx yum repository first.

nginx yum repository setup
  1. Download the rpm to configure the repository, this is available at:
    http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm (notice that I'm using CentOS 6, check the url for other O/S)
  2. Install the rpm
  3. Import nginx keys
  4. Optionally but strongly recommended: edit /etc/yum.repos.d/nginx.repo and change the value of gpgcheck to 1
Below are the commands to complete steps 1-3:

wget http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
rpm -ivH nginx-release-centos-6-0.el6.ngx.noarch.rpm
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-nginx
nginx setup
The simplest as it can be:

yum install nginx
# nginx can now be started using the following command:
/etc/init.d.nginx start


To verify that everything is working access to http://ip, if everything is OK the nginx welcome page should appear.

uWSGI

This is a little bit more complicated story, uWSGI has to be built from the source (at least I didn't find any rpm for it). However it is not too complicated:

Building uWSGI
  1. Prerequisites: since I compile from source there are probably some prerequisites need to be satisfied. I didn't have any problem but it doesn't mean that other machines will not be missing some of it. if the build fails I would check for missing packages based on the error message.
  2. Download from uWSGI site (I used version 1.0.1):
  3. wget http://projects.unbit.it/downloads/uwsgi-1.0.1.tar.gz
  4. Open the tar file and change directory to the newly created folder
  5. tar xvzf uwsgi-1.0.1.tar.gz && cd uwsgi-1.0.1
  6. Setup and run the build
  7. python setup.py build 
    make
  8. A new executable file named 'uwsgi' should be created
  9. To test that uwsgi is built correctly and can do 'something' start it using its embedded HTTP server (-http flag) and try accessing it from a browser.
    1. The command 'uwsgi -http 127.0.0.1:8888' will start the server listening for http on port 8888. 
    2. Accessing the server on that address should generate an error page (complaining that a Python application cannot be found) but this is a good sign. 
    3. Shutdown the server (kill -2 does the work)
So far we installed and started both nginx and uWSGI however there is no connection between the two - each is a standalone server. The next step would be to create that connection.

'Pairing' nginx and uWSGI

 

uWSGI
The '--help' flag prints out uWSGI's detailed help however here are the flags I would start with:

--socket the socket to bind to (to this nginx will connect)
--master enable master process, this is useful for an easy management of 
    the process pool
--workers <n> number of workers to start
--harakiri <n> when harakiri mode is enabled any request takes longer 
    than <n> seconds will be dropped and the corresponding worker 
    will be recycled
--disable-logging disable requests logging (useful for reducing the logging volume 
    - only errors are logged)
--daemonize <logfile> daemonize and log into the specified log file
--pidfile <path> where to write the master pid to
--vacuum clear the environment on exit (remove UNIX sockets and pidfiles).
    BTW: it didn't clean the pid file on my environment but it worth trying.
--max-requests maximum number of requests for each worker before recycling
--gid <id/name> setgid to <id/groupname>
--uid <id/name> setuid to <id/username>


Start the uWSGI using the following command (a reminder: this will not be able to serve any Django application yet - at the moment I'm just 'pairing' it with nginx), obviously the uid, gid and files location are just for the sample but the command bellow should work on any environment:



uwsgi --socket 127.0.0.1:8889 --master --workers 1 --harakiri 30 
 --disable-logging --daemonize /tmp/daemonize.log 
 --pidfile /tmp/pidfile.txt --vacuum --max-requests 100 
 --gid 500 --uid 500 --vacuum


Notice that uWSGI doesn't provide a startup script (/etc/init.d/uwsgi) in the distribution and I am not providing one in here either, however it should be simple enough to build one to start uWSGI using the flags described above.

nginx
I should now configure nginx to rout requests to uWSGI server, nginx's configuration is in the /etc/nginx/conf.d folder. Backup aside the default.conf file in that folder and replace with the following configuration file which will configure nginx to direct requests to the uWSGI server stared in the previous step:



server {

 listen 80;
 server_name <replace with your hostname>;
 
 #Replace paths for real deployments...
 access_log /tmp/access.log; 
 error_log /tmp/error.log;

 location / {
 include uwsgi_params;
 uwsgi_pass 127.0.0.1:8889;
 }
}


The above configuration directs all requests accepted by the nginx server to uWSGI server listening to port 8889 on the localhost. Restart nginx (/etc/init.d/nginx restart) and use the browser to access nginx on port 80. We should get an error but notice that this time the error is generated by uWSGI and not by nginx (even though we accesses port 80) - nginx and uWSGI are now 'paired'.

Django

The last step is to configure uWSGI to execute my Django project and to point nginx to the Django's static resources location. For the next section I'll assume the following as my Django project's configuration:

  • My Django site is in /home/eyal/mysite
  • This site has one application in the directory /home/eyal/mysite/myapp, and
  • The application's static resources are in /home/eyal/mysite/myapp/static
Django Configuration Module
Create a new Python module named uwsgiApp in the site folder (this name ' uwsgiApp' is not mandatory, but it is the one I am about to use below so if changed the uWSGI startup command below should be changed as well); the module should be similar to the example below (pay special attention to the path and packages):



import os, sys
sys.path.append('/home/eyal')
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

import django.core.handlers.wsgi

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


Notice that I'm using the folder above 'mysite' to append to the path and include the 'mysite' package in the DJANGO_SETTINGS_MODULE environment variable.

nginx
Add the following section to your nginx's configuration to map static resource (again: pay special attention to the path and packages):



location /static {
 root /home/eyal/mysite/myapp;
}


It is important to notice that I have dropped the 'static' element from the filesystem path (think about it as if it was moved to the location definition itself). Now nginx configuration file should look like that:



server {

 listen 80;
 server_name <replace with your hostname>;
 
 #Replace paths for real deployments...
 access_log /tmp/access.log; 
 error_log /tmp/error.log;

 location / {
   include uwsgi_params;
   uwsgi_pass 127.0.0.1:8889;
 }
 
 location /static {
   root /home/eyal/mysite/myapp;
 }

}


Restart nginx to make sure the new configuration applies.

uWSGI
Fitst I have to shutdown the already running uWSGI (kill -2 to the uWSGI's master process pid). Add the two flags below to the ones used in previous uWSGI startups:

--pythonpath directory to add to the pythonpath
--module name of python config module


Using the Django configuration provided above the new start command will be:



uwsgi --socket 127.0.0.1:8889 --master --workers 1 --harakiri 30 
   --disable-logging --daemonize /tmp/daemonize.log --pidfile /tmp/pidfile.txt 
   --vacuum --max-requests 100 --gid 500 --uid 500 
   --pythonpath /home/eyal/mysite --module uwsgiApp




That's it - pointing the browser to port 80 should now display your Django application through nginx and uWSGI.

Comments

Frank Rommer said…
Reminds me on this blog entry: http://www.westphahl.net/blog/2010/4/8/running-django-nginx-and-uwsgi/
Tony K said…
Thanks, was really helpful for me :)
grails cookbook said…
Nice guide. It really shows how simple and flexible nginx is. With just few configuration, it is nicely integrated with uwsgi
Dylan said…
Thank you!!!!!
This guide is fantastic, I have learned very much from your post! Very informative, not just copy/paste code. Really appreciate the time you took to make this :)
I have learned so much from your post. I would definitely bookmark your site to be updated with your upcoming articles. Great job! So much information.

Popular posts from this blog

New in Spring MVC 3.1: CSRF Protection using RequestDataValueProcessor

Hibernate Exception - Simultaneously Fetch Multiple Bags

Hibernate Derived Properties - Performance and Portability