"Bad request 400" in Django. Beyond the ALLOWED_HOSTS

Mar 7, 2016 17:27 · 313 words · 2 minute read python django nginx snippet

Missing ALLOWED_HOSTS in settings.py


ALLOWED_HOSTS = ("example.com",)

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

Recently, while deploy a Django application with gunicorn and nginx I’ve encountered with 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 many 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;
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

Despite the fact that domain name can actually contain the underscores, the host names can not. Thus, Django wont validate it even tho 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.

We will notify you about new posts every few weeks