Fixing the damn ALLOWED_HOSTS issue with Amazon Elastic Beanstalk and Django
I’ve been banging my head against this one for weeks and finally got on top of it.
Django has a mechanism to prevent HTTP Host header attacks and is best practice to set a ALLOWED_HOSTS
variable in your settings with the known hosts and URLs your application you’ll run in.
Trick is, if you’re using Amazon Beanstalk it sometimes will deploy your application in a random host with an IP like 3.12.70.128 (example), and that is a random number, which means you can’t list that in your ALLOWED_HOSTS
setting. That means a whole bunch of noise and if you like me want to keep track of your production issues in Sentry, your quota will evaporate really quickly as health checks can happen thousands of times a day.
There were a few solutions suggested in StackOverflow but all of them missed the catch where it looks like in some cases the machine seems to have a hostname but queries the application via the IP. (I don’t fully understand what happens, any education is welcome). I finally managed to del
Steps
Login to the EB EC2 instance.
Make a copy of the /etc/nginx/nginx.conf file. ABSOLUTELY DO THIS - I recommend putting that in your application code should the future require you to roll back.
Create a directive to replace calls from IP with a custom hostname that you can add to ALLOWED_HOSTS.
Copy the contents of /etc/nginx/conf.d/elasticbeanstalk/00_application.conf and put them in the server context of the above.
Update the proxy_set_header in the location directive to
$my_host
.Comment out or remove the directive
include conf.d/elasticbeanstalk/*.conf;
from your nginx.conf file.
What does it look like
In the root of your application create a file under .platform/nginx/nginx.conf

You might have to adapt the code below depending if you have any specifics fo your setup, so check the comments to what actually does the magic.
user nginx;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 200000;
events {
worker_connections 1024;
}
http {
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
map $http_upgrade $connection_upgrade {
default "upgrade";
}
server {
listen 80 default_server;
access_log /var/log/nginx/access.log main;
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip off;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# ELB MAGIC HERE - if we detect an IP, set host to a custom domain
set $my_host $host;
if ($host ~ "\d+\.\d+\.\d+\.\d+") {
set $my_host "elb.mydomain.com"; # ADD THIS DOMAIN TO ALLOWED HOSTS
}
location / {
proxy_pass http://127.0.0.1:8000 ;
proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
# THE BELOW WILL SET THE DOMAIN TO YOUR CUSTOM DOMAIN ABOVE IF IT'S FROM AN IP
proxy_set_header Host $my_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static {
alias /var/app/current/static;
access_log off;
}
# THE EXTRA FILES FROM ELB CONTAIN THE location / DIRECTIVE BELOW
# P.S. I don't love this
# Include the Elastic Beanstalk generated locations
# include conf.d/elasticbeanstalk/*.conf;
}
}