How to fix HTTP_HOST Errors with Django, Nginx, and Let's Encrypt

Django has a nice security feature that verifies the request HOST header against the ALLOWED_HOSTS whitelist and will return errors if the requesting host is not in the list. Often you’ll see this when first setting up an app where you only expect requests to app.example.com but some bot makes a request to <server ip address>.

While it’s not strictly harmful to add your server ip to your ALLOWED_HOSTS, in theory, it does allow bots to easily reach and fire requests to your Django app, which will needlessly consume resources on your app server. It’s better to filter out the requests before they get to your app server.

For HTTP requests, you can block requests by adding default_server that acts as a catchall. Your app server proxy then set its server_name to the a domain in your ALLOWED_HOSTS. This simple configuration will prevent http://<server ip address> requests from ever reaching your app server.


// default.conf
server {
listen 80 default_server;
return 444;
}

// app.conf
upstream app_server {
server 127.0.0.1:8000 fail_timeout=0;
}

server {

listen 80; server_name {{ WEB_SERVER_NAME }};
access_log /var/log/nginx/access.log access_json;
error_log /var/log/nginx/error.log warn;

location /static/ {
alias /var/app/static/;
}

location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Request-Id $request_id;
proxy_redirect off; proxy_pass http://app_server;
}
}

However, once you enable SSL with Let’s Encrypt, despite the fact that they matching by host, as there is only one SSL server configuration by default, it routes all https traffic to the same host. What this means is thatΒ while requests made to http://<server ip address> will continue to be blocked, requests to https://<server ip address> will begin to be forwarded to your django app server, resulting in errors. Yikes!

The solution is to add a default SSL enabled server, much like your http configuration. Thee only tricky bit is that all ssl configurations must have a valid ssl certificate configuration as well. Β Rather than making a self-signed certificate I reused my let’s encrypt ssl configuration.

// default.conf
server {
listen 80 default_server; return 444;
}

server {
listen 443 ssl default_server;
ssl_certificate /etc/letsencrypt/live/{{ WEB_SERVER_NAME }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ WEB_SERVER_NAME }}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

if ($host != {{ WEB_SERVER_NAME }}) {
return 444;
}
}

By adding a default SSL server to your nginx config your server_name settings will be respected and requests that do not match your host name will no longer be forwarded to your app server.