Django / Nginx — Making SSL Work on Django Behind a Reverse Proxy & HTTP Only Apache

by Yuji

Update: django 1.4 has a new feature that allows you to specify which HTTP header implies SSL via a new setting SECURE_PROXY_SSL_HEADER, but it comes with dire warnings that your front end proxy MUST strip this header if not secure so that the user can not set the header manually to imply an SSL secure connection.

I didn’t think of this, and it’s a great point… my example did not explicitly set the flag OFF if not on port 443.

How to make SSL work on Django when the certification is done on the front-end proxy server & apache is only listening on http.

I have nginx working as a reverse proxy for Apache / mod_python serving Django, somewhat according to the instructions here:

Basically, Nginx sits in the front, and forwards all requests but /media/ to apache.

My problem arose when I wanted to implement this SSL Redirect middleware:

The setup, and why the middleware loops infinitely

I have nginx dealing with the SSL, forwarding requests to the same apache vhost as regular http, so apache is blissfully unaware of SSL.

This means request.is_secure() always returns False, which means the SSLRedirect middleware endlessly loops around a redirect because is_secure keeps returning False.

The fix

Add some extra header to the request in Nginx that specifies an HTTPS connection

To fix the problem I just set up my nginx to add a header “HTTP_X_FORWARDED_PROTOCOL” = “https”

Next, I replaced the request.is_secure() in the SSLRedirect middleware with a check to first see if the above mentioned header is in request.META, and then if its value is ‘https’. Return true if that is the case, and we successfully get a redirect.

Edit the SSLRedirectMiddleware

Edit the middleware to check for our secure header.

You must edit the above mentioned snippet somewhere in the _is_secure(self, request): definition.

Modify to:

def _is_secure(self, request):
     if request.is_secure():
         return True

     if 'HTTP_X_FORWARDED_PROTOCOL' in request.META:
         return True

Edit nginx conf

Edit nginx.conf (mine lives in /etc/nginx/nginx.conf) wherever you have your nginx listening on port 443.

Add a the custom header to your configuration where you have your other headers set (right inside the location brackets).

    proxy_set_header X-Forwarded-Protocol https;

Restart and you’re done!

Restart nginx and apache and you are good to go.