Local Django development with Nginx

11 October 2010   12 comments   Django

Powered by Fusion×

When doing local Django development with runserver you end up doing some changes, then refreshing in Firefox/Chrome/Safari again and again. Doing this means that all your static resources are probably served via Django. Presumably via django.views.static.serve, right? What's wrong with that? Not much, but we can do better.

So, you serve it via Nginx and let Nginx take care of all static resources. You'll still use Django's own runserver so no need for mod_wsgi, gunicorn or uWSGI. This requires that you have Nginx installed and running on your local development environment. First you need to decide on a fake domain name. For example mylittlepony. Edit your /etc/hosts file by adding this line:       mylittlepony

Then create the file /etc/nginx/sites-available/mylittlepony and type something like this in it:

server {
   root /home/peterbe/projects/mylittlepony/static;
   server_name mylittlepony;
   gzip            off;

   location = /favicon.ico  {
       rewrite "/favicon.ico" /img/favicon.ico;
   proxy_set_header Host $host;
   location / {
     if (-f $request_filename) {
         add_header X-Static hit;
         access_log   off;

     if (!-f $request_filename) {
         add_header X-Static miss;

Then when you've done that enable it:

# cd /etc/nginx/sites-enabled
# ln -s ../sites-available/mylittlepony
# /etc/init.d/nginx reload

Now test the site with curl or something:

$ curl -I http://mylittlepony/ 
HTTP/1.1 200 OK
Server: nginx/0.7.65
Date: Fri, 08 Oct 2010 14:35:04 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Expires: Fri, 08 Oct 2010 14:35:04 GMT
Vary: Cookie
Last-Modified: Fri, 08 Oct 2010 14:35:04 GMT
ETag: "fecf14808e52fe8652373f6e49e1ac06"
Cache-Control: max-age=0
Set-Cookie: csrftoken=f6eb9e767ca058ecde24bb51c8db9448; Max-Age=31449600; Path=/
Set-Cookie: sessionid=c48af92360ad29410d199081e6067f54; expires=Fri,  
  22-Oct-2010 14:35:04 GMT; Max-Age=1209600; Path=/
X-Static: miss

Now test to get a static resource:

$ curl -I http://mylittlepony/css/jquery-ui-1.8.4.css
HTTP/1.1 200 OK
Server: nginx/0.7.65
Date: Fri, 08 Oct 2010 14:36:31 GMT
Content-Type: text/css
Content-Length: 24806
Last-Modified: Tue, 31 Aug 2010 18:11:38 GMT
Connection: keep-alive
X-Static: hit
Accept-Ranges: bytes

Awesome! If you're curious how much faster Nginx is than Django's serve view, here's a hasty benchmark from my laptop:

# ab -n 10000 -c 10 http://localhost:8000/css/jquery-ui-1.8.4.css
Requests per second:    446.34 [#/sec] (mean)
Time per request:       22.405 [ms] (mean)
Time per request:       2.240 [ms] (mean, across all concurrent requests)
Transfer rate:          10893.44 [Kbytes/sec] received

And the same directly from Nginx:

# ab -n 10000 -c 10 http://mylittlepony/css/jquery-ui-1.8.4.css
Requests per second:    15709.54 [#/sec] (mean)
Time per request:       0.637 [ms] (mean)
Time per request:       0.064 [ms] (mean, across all concurrent requests)
Transfer rate:          384039.88 [Kbytes/sec] received

Obviously you're not going to be able to hit your website that hard but trust me, when you work like this a lot and do a lot of refreshing over and over (working on some Javascript code for example) you can really feel the difference. The static resources load faster because and Django just have to do one single thing which is to create the HTML page. Your stdout on the runserver is only going to log actual views used. Like this:

[08/Oct/2010 15:43:14] "GET / HTTP/1.0" 200 8639
[08/Oct/2010 15:43:27] "GET /crm/clients/ HTTP/1.0" 200 28830
[08/Oct/2010 15:43:30] "GET /crm/clients/A1215/ HTTP/1.0" 200 12804

I hope this helps other people shave milliseconds off their development time.


I'd been using Apache in a similar way for quite a while now but switched to Nginx a few month ago, much snappier. Perfect for the job. Just a suggestion for the conf: The encouraged convention instead of ``if`` blocks is using try_files and named locations, something like:

location / {
try_files $uri $uri/ @django;

location @django {
# etc.
Excellent tip! Thanks. I guess I haven't updated my nginx-fu.
Olivier Favre-Simon
Nice. Yes nginx is great as django front-end.

Your example would be completely functional with a reminder to add line

include /etc/nginx/sites-enabled/mylittlepony;

to /etc/nginx/nginx.conf because this is not in the default config in some setups (e.g. mine is www-servers/nginx-0.8.52 on a Gentoo Linux box).
Default on Ubuntu and Debian :) It also creates the directories.
Olivier Favre-Simon
Also it would be quite fair to show results of ab2 with setup like http://docs.djangoproject.com/en/dev/howto/static-files/

that is with a line like
#(r'^mylittlepony/media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/path/to/mylittlepony/media'}),

at the top of urlpatterns in urls.py.
Nginx is much faster than 'django.views.static.serve'.
Dennis Fisher
This worked well for me except for one thing; I kept getting 502 errors on just http://hostname/, but http://hostname/blah.txt would work fine. I looked around a bit online and eventually tried just one tweak, which was removing the proxy_pass line in the server conf. Files still seem to get served just fine, and the 502 error is gone. What is the purpose of the proxy_pass line?
Dennis, proxy_pass is needed to actually, well, proxy the non-static request to django, so you will need it! Maybe the 502 comes because there is no index.html - but with the try_files directive this should not happen.
I had the same issue, until I realized I hadn't started my devserver. Doh!
Victor Hooi

How would this handle Django admin resources?

Not sure what's the best way to setup settings.py and Nginx to handle this?

Thanks :)
Anshuman Aggarwal
Minor improvement suggestion:

proxy_set_header Host $http_host; #instead of $host, allows for proxy port retention etc.

Your email will never ever be published

Related posts

In jQuery, using the :visible selector can be dangerous 14 September 2010
My tricks for using AsyncHTTPClient in Tornado 13 October 2010
Related by keywords:
Fastest way to uniqify a list in Python 14 August 2006
fcgi vs. gunicorn vs. uWSGI 09 April 2010
Is Nginx obsolete now that we have Amazon CloudFront? 28 July 2012
How I stopped worrying about IO blocking Tornado 18 September 2012
The awesomest way possible to serve your static stuff in Django with Nginx 24 March 2010
Optimization of getting random rows out of a PostgreSQL in Django 23 February 2011
How to uninstall nginx with apt 28 March 2008
How much faster is Nginx+gunicorn than Apache+mod_wsgi? 22 March 2012
Speed of DoneCal API (over 1,400 request/sec) and HTTPS (less than 100 request/sec) 27 December 2010
Getting uploadify to work 17 July 2009
How I profile my Nginx + proxy pass server 16 February 2011
What makes my website slow? DNS 23 October 2009