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:

127.0.1.1       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) {
         proxy_pass http://127.0.0.1:8000;
         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.

Ray - 11 October 2010 [«« Reply to this]
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 {
proxy_pass http://127.0.0.1:8000;
# etc.
}
Peter - 11 October 2010 [«« Reply to this]
Excellent tip! Thanks. I guess I haven't updated my nginx-fu.
Olivier Favre-Simon - 11 October 2010 [«« Reply to this]
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).
Peter - 15 October 2010 [«« Reply to this]
Default on Ubuntu and Debian :) It also creates the directories.
Olivier Favre-Simon - 12 October 2010 [«« Reply to this]
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.
Peter - 15 October 2010 [«« Reply to this]
Nginx is much faster than 'django.views.static.serve'.
Dennis Fisher - 26 October 2010 [«« Reply to this]
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?
Snaky - 26 October 2010 [«« Reply to this]
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.
mikeshantz - 27 October 2010 [«« Reply to this]
I had the same issue, until I realized I hadn't started my devserver. Doh!
Victor Hooi - 29 October 2010 [«« Reply to this]
heya,

How would this handle Django admin resources?

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

Cheers,
Victor
Kevin - 08 May 2012 [«« Reply to this]
Thanks :)
Anshuman Aggarwal - 02 July 2012 [«« Reply to this]
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