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.
Comments
Post your own commentI'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.
}
Excellent tip! Thanks. I guess I haven't updated my nginx-fu.
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.
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'.
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!
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
Thanks :)
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?
Thanks
Like this:
server{
location / {
try_files $uri @django @npm;
}
location @django {
proxy_pass http://127.0.0.1:8000;
}
location @npm {
proxy_pass http://127.0.0.1:3000;
}
}
Cool, thanks!