The Great Magnet. What a fool I was to defy him.

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

August 15, 2008 · 5 Comments

How to make SSL work on Django when the certification is done on the front-end 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:

http://lethain.com/entry/2007/jul/17/dreamier-dream-server-nginx/

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: http://www.djangosnippets.org/snippets/880/

Last time I set up SSL on Django, I had nginx forward HTTPS requests to a separate apache virtual host, also listening on port 443. The SSL certification was on apache, so Djangos request.is_secure() returned True with no problems.

This time though, I have nginx dealing with the SSL, and forwarding the request to the same apache as regular http (one apache vhost), 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. Endlessly redirecting because is_secure() always returns False.

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

and replaced the request.is_secure() in our 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.

I guess the value of it doesn’t matter, just that it exists vs not, but that feels very dirty.

Update:

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

Add:

if 'HTTP_X_FORWARDED_PROTOCOL' in request.META:
return True

and edit nginx.conf (mine lives in /etc/nginx/nginx.conf):
Wherever you have your nginx listening on port 443, you know, something like server { listen 443; ...:

add a proxy_set_header X-Forwarded-Protocol https; to your configuration where you have your other headers set (right inside the location brackets).

Restart nginx and apache and you are good to go.

Categories: Django · Nginx
Tagged: , ,

5 responses so far ↓

  • mark // September 10, 2008 at 5:51 pm | Reply

    I’ve got the same issue, except using apache as reverse proxy, but it also terminates SSL.

    And in my config, it won’t loop, just lands on apache on the SSL enabled port trying to do HTTP and apache kills the request.

    Your approach is exactly how I was going to do fix this too. This really will be an issue in general as django apps are deployed in larger scale environments where SSL between border reverse proxy and app server is useless. I hope Django folk will put a solution in core.

  • Yuji // September 15, 2008 at 8:39 am | Reply

    Hey Mark,

    Yeah, the SSL between the proxy and app server really is useless huh? I’m very new to all of this and when it came time to set up SSL I wondered how to get around setting up another vhost just to receive https on the app server.

    I’m glad the solution was easy, because most server admin stuff goes right over my head.

    Thanks for the comment!

  • Josh Martin // September 16, 2008 at 10:30 pm | Reply

    I was setting up SSL for nginx and found that Rails actually wants

    proxy_set_header X-Forwarded-Proto https;

    to be able to have @env['HTTP_X_FORWARDED_PROTO'] == ‘https’ when calling the request.ssl? method to return true.

  • Yuji // September 16, 2008 at 10:40 pm | Reply

    Hi Josh,

    Interesting that rails does that. Come to think of it, I don’t think I ever checked what exactly request.is_secure does. Wonder if it also listens for some default header flag?

    In my example the header name is arbitrary because I was just coaxing the redirect middleware to work.

  • hui // October 14, 2009 at 8:30 pm | Reply

    I’m using ‘django.contrib.csrf.middleware.CsrfMiddleware’ and forwarding admin site to ssl. Now I’m getting cross site forgery detected when I login to admin site. Anyone know how to get around this without disabling the middleware?

Leave a Comment