Django Behind a Proxy

Aashish Nehete
WorkIndia.in
Published in
2 min readApr 13, 2020

With the rise in number of microservices being used at WorkIndia, the old system of using a separate subdomain for each service along with a dedicated Elastic Load Balancer was quickly proving to be too expensive. Thus, a decision was taken to move all microservices under single subdomain using a single ELB. We also added a layer of Cloudfront to decrease costs.

But soon, we ran into some problems…

Incorrect Host Headers

The addition of Cloudfront proxy meant that some Django applications won’t get the correct Host headers. This generally isn’t a big deal, but one of our services used this extensively. (Pagination using Django Rest Framework uses it to create next and previous url)

For eg, Without a proxy, the host header for a sample request like https://api.workindia.in/api/health/ looked like this:
Host: api.workindia.in

But with a proxy, the header changed to Host: api.workindia.in:20000, because that was the port on which our ELB was configured.

Solution

Luckily, the developers of Django foresaw this and created a setting that could override the Host header with a custom header X-Forwarded-Host.

USE_X_FORWARDED_HOST = True

Adding the above line in your settings file will make the application first check the X-Forwarded-Host. If the request contains X-Forwarded-Host header, it will be used as the value for current host.

X-Forwarded-Host Header Not Present

Feeling confident, I deployed the service in production. But to my disappointment Django was still using the value being passed in the Host header.

The above solution wasn’t enough, because neither Cloudfront nor the Load Balancer set the X-Forwarded-Host header automatically. Thus the request didn’t contain the required header.

Solution

Cloudfront allows you to add custom headers to an origin which will be attached to all requests to that origin. Following this guide, we added the following header X-Forwarded-Host: api.workindia.in.

Incorrect Protocol — HTTP instead of HTTPS

Now that our request had the X-Forwarded-Host header, we were able to get the port out of our host. Yet, after deployment we couldn’t get it to work.

I realised that although the host was correct in the next url, the protocol wasn’t. Instead of https, the url was using http.

Solution

Again Django settings to the rescue. The SECURE_PROXY_SSL_HEADER setting can be used to specify which header and value determines if the request is https or http.

It takes a tuple with exactly 2 element:(Header-Name, Header-Value)

For eg. SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

(Remember, Django automatically adds 'HTTP_' to the start of x-header names before making the header available in request.META.)

This tells Django to trust the request is secure (i.e.https) if the header X-Forwarded-Proto has a value of https.

Although, this didn’t work in my case because the ELB would always override X-Forwarded-Proto to http.

So, I added a custom header to my Cloudfront Origin (like we did above) called X-Custom-Proto: https

I also changed the settings file as such:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_CUSTOM_PROTO', 'https')

Et voilà! Everything started working perfectly.

--

--

Aashish Nehete
WorkIndia.in

Yet Another Computer Engineer \ …also: Writer | Musician | Film Maker