XOR Media

Django ALLOWED_HOSTS and Amazon Elastic Load Balancer

A recent Django security release added a new configuration option, ALLOWED_HOSTS. It’s optional in 1.3 and 1.4, but required in 1.5 setups. The relevant detail is that when enabled in production Django will throw 500 errors if the Host header doesn’t match one of the values in ALLOWED_HOSTS.

The good news is that ELB preserves the Host header on requests it forwards, as it would have to. The bad news is that it doesn’t set a useful Host header during it’s health and latency checks which causes them to fail and mark the host as down. In fact, the two user different values.

The health check uses the instance’s internal IP address which can be determined and added to the ALLOWED_HOSTS list. The latency check on the other hand uses another IP address that doesn’t belong to any instances that I own. My guess would be that it’s the IP address of the ELB node itself, but I have not had a chance to test/verify that.

So by finding and adding the internal IP address to ALLOWED_HOSTS you can make the health check happy and at least get your instances serving requests, but the latency checks will still be 500’ing and there’s no clean way to fix that.

The health check problem coupled with the PITA of reliably getting the internal IP address of a host lead me to look for a better solution. What I ended up on involved munging the Host header if it looks like an IP address. To do this I added the following my nginx site config.

set $my_host $host;
# if the host header is an ip address change it to www.mysite.com
# this works around requests coming from ELB with either the instance's
# internal ip address in the case of health checks or an unknown internal
# ip address in the case of latency checks. translating them to a known
# good host header makes django's ALLOWED_HOSTS happy
if ($host ~ "\d+\.\d+\.\d+\.\d+") {
    set $my_host "www.mysite.com";
}

location @django {
    proxy_set_header Host $my_host;
    ...

The above initially sets $my_host to the $host header from ELB. It then checks to see if it looks like a 4-segment IP address. If it does set $my_host to the default “www.mysite.com.” Finally $my_host, whether defaulted or overridden, is passed along to Django.