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

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.

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

  1. 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.

  2. 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!

  3. 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.

    1. Django will be happy with this also if you set:


      in your settings file.

  4. 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.

  5. 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?

  6. Hi: Sorry if this is a newbie question, but how does it work that you were able to have nginx and apache listening on the same port (443)? Wouldn’t they conflict? How would you set nginx to forward to Apache listening on the same port?


  7. Hey Eric,

    You bring up a good point… I don’t remember what I actually had setup.

    It would conflict wouldn’t it. I wonder if I had apache on a different port.

    Maybe ngxing wasn’t even dealing with 443? hmmm that sounds odd though.


  8. Hi Yuji:

    Thanks for the response. I’m particularly interested because I’m trying to do something similar. I had the normal setup: nginx passing an HTTP request over to Apache listening on port 8000. The only thing I’ve done so far in setting up SSL is to tell nginx where the certificate and key are and to listen on 443. This results in being able to go to a page using https://, but clicking an internal link will no longer work and actually tries to reach the link over regular http://.

    I haven’t made any changes to Apache or Django yet, but it seems like I’d have to as Django does not recognize that the connection is secure, since Apache also does not know that the connection is secure. I feel like I’d have to do something in my virtual host settings, like somehow tell mod_python to check if the request is secure and then pass this information to Django, but I’m not sure how to go about doing this.

    Anyway, I know it’s been a while, but any insight would be greatly appreciated.



  9. Hey Eric,

    How do the internal links no longer work? It seems like your whole site could load in https now, but that’s the problem: you only want a few pages to do that.

    This issue is exactly what the post is about! /because/ we’re handling the SSL with nginx, and apache doesn’t even know about it, we have to pass in a custom header to the SSL requests that nginx proxies to apache that Django can check for.

    In my example I’m using an SSL redirect middleware (link above) so that pages like /checkout/ redirect to https~/checkout. The middleware literally checks the headers and returns an “is_secure” flag when it finds a specific header we set in the nginx server config.

    This way, django can check if the connection is secure via the headers in request.META and you’re free to do whatever.

  10. Hi Yuji

    Thanks again for the reply. I found out what was wrong. It actually had nothing to do with my nginx/apache/django settings. I’ll explain it here in case anyone has this problem.

    I realized that almost all of the links on my site did not have trailing slashes even though the urls in urls.conf had them. The default behavior is to automatically redirect to a version of the url that has that trailing slash. However, for me, the redirect was dropping the https (probably because Django is completely unaware of the secure connection). So all I had to do was make sure my urls were correct and everything was fixed.

    It’s kind of a weird problem. Thanks for you help anyway.


  11. Yuji,

    Thanks for this post. It inspired me while having similar problem using nginx+uwsgi.

    When using nginx and uwsgi (instead of apache) and mentioned snippet,
    in location section of nginx it was enough for me to put this line:

    uwsgi_param HTTP_X_FORWARDED_SSL on;

    1. amackerathm, that’s a very nice new django feature! Thanks for showing it to me.

      Please note to readers this is a 1.4 feature not yet in stable django release – and that it has a dire warning to make sure users can’t set this flag (so your nginx config needs to strip this if not on port 443) which is a great point… I need to implement that!!!

      I will add it to the post.

  12. conclusion 2 solutions:

    if you use nginx + Django <= 1.3.1 in HTTPS (SSL) use solution A:
    nginx setting (in django 'location' work well):
    proxy_set_header X-Forwarded-Protocol https;
    + need to apply this snippet middleware:

    note: change the HTTP_X_FORWARDED_SSL to fit with your nginx setting (HTTP_X_FORWARDED_PROTOCOL)

    If you are with Django >= 1.4.x (released now) use solution B: X-Forwarded-Proto

    note: solution A is more flexible, like if the web site is in both HTTP and HTTPS only solution a work (more on top of the transaction).

  13. Note that it should probably be

  14. Greetingѕ from Ohiо! ӏ’m bored to tears at work sߋ I decided to check out your ԝebsitе on my iphone during luncɦ break.
    I love the knowledge you ρrovide here and can’t wait to
    take a look when I get home. I’m amazed at how
    quick your blog loaded on my phone .. I’m not even սsing WIFI, just 3G ..
    Anyhow, verty good site!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s