Django sending 400 "Bad request"

Mar 7, 2016 17:27 · 320 words · 2 minute read Python Django

Missing ALLOWED_HOSTS

In settings.py

ALLOWED_HOSTS = ("example.com",)

where example.com is actually the domain you are running the app on. That did the trick. The thing is that in production mode (DEBUG=False), Django doesn’t serve all domains.

Recently, while deploying a Django application with gunicorn and nginx I encountered the following problem: application returns Bad Request 400 for all requests. The solution was simple - just add this to your settings.py file.

A list of strings representing the host/domain names that this Django site can serve. This is a security measure to prevent an attacker from poisoning caches and triggering a password reset emails with links to malicious hosts by submitting requests with a fake HTTP Host header, which is possible even under seemingly-safe web server configurations. ALLOWED_HOSTS Django docs - official documentation for further information. This will work with both Nginx and Apache.

Nginx doesn’t pass $host to your application

Make sure the nginx config is configured to pass the Host variable via proxy. This is done via adding the following to the location directives:

proxy_set_header Host $host;
and
 
proxy_set_header X-Forwarded-Host $server_name;

Full example of the configuration may look like follows:

server {
  listen      80;
  server_name example.com;
  access_log off;

  location /static/ {
      alias /opt/projectname/static/;
  }

  location / {
      proxy_set_header Host $host;
      proxy_pass http://localhost:8000;
      proxy_set_header X-Forwarded-Host $server_name;
      proxy_set_header X-Real-IP $remote_addr;
  }
}

Host name has underscores

Sometimes, Django sending 400 Bad request because of domain name. Despite the fact that domain name can actually contain the underscores, the host names can not. Thus, Django won’t validate it even though you have ALLOWED_HOSTS settled correctly. Here is the regular expression that Django actually uses to validate the host name:

host_validation_re = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9:]+\])(:\d+)?$")
As you can see, there is no room for underscores. In this case you should either consider to change the host name or patch the Django’s validation function. Particularly, change the host_validation_re to the one that matches your host name.