Local Django development with Nginx

11 October 2010   15 comments   Django

Mind That Age!

This blog post is 7 years old! Most likely, it's content is outdated. Especially if it's technical.

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.
Cool - I may have to try this.

Any ideas how to proxy two or more Django dev servers under one NGINX proxy in this manner?

Peter Bengtss
Like this:

        location / {
        try_files $uri @django @npm;
    location @django {
    location @npm {
Cool, thanks!
Thank you for posting a comment

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
How to deploy a create-react-app 04 November 2016
Benchmarking Autocompeter 12 April 2015
uwsgi and uid 03 November 2014
django-fancy-cache with or without stats 11 March 2013
How I stopped worrying about IO blocking Tornado 18 September 2012
Is Nginx obsolete now that we have Amazon CloudFront? 28 July 2012
Secs sell! How frickin' fast this site is! (server side) 05 April 2012
How much faster is Nginx+gunicorn than Apache+mod_wsgi? 22 March 2012
Goodies from tornado-utils - part 2: tornado_static 22 September 2011
Optimization of getting random rows out of a PostgreSQL in Django 23 February 2011