Passing real visitor IP addresses to Django 1.10+ with nginx and Cloudflare

If you entrust your website to Cloudflare - as is the case with this site - you've probably noticed that out of the box, all of the hits to your web application appear to come from Cloudflare IP addresses. Of course, this is a typical side effect of any reverse proxy setup. If you've got a Django/nginx-powered site sitting behind Cloudflare, it's easy enough to pass the original visitor IP address through to nginx (and subsequently to Django) with some minor configuration tweaks and a few lines of code. This writeup is aimed towards developers using Django 1.10 or newer and the new-style middleware.

Setting up nginx

First, you'll need to tell nginx to recognize Cloudflare's IP ranges and the HTTP header Cloudflare uses to pass the original visitor IP. Cloudflare has published an excellent, concise info page on how to do this.

We just need to make one more minor addition to the nginx configuration. Since nginx itself is also acting as a reverse proxy to your Django instance (e.g. gunicorn or uwsgi), the real IP address must be passed through again via an HTTP header. X-Real-IP is more or less the de facto standard name for this.

In your nginx global configuration or a vhost/server block:

proxy_set_header X-Real-IP $remote_addr;

If you're including the /etc/nginx/proxy_params present on Ubuntu and some other Linux distributions, this directive may already be present.

Setting up Django

Finally, we can add some simple middleware to our Django app in order to apply the value of our X-Real-IP header.

Using the new-style middleware classes introduced in Django 1.10:

class XRealIPMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response
    def __call__(self, request):
        try:
            real_ip = request.META['HTTP_X_REAL_IP']
        except KeyError:
            pass
        else:
            request.META['REMOTE_ADDR'] = real_ip
        return self.get_response(request)

Don't forget to add this middleware to the list of MIDDLEWARE in your settings.py.

Now you'll be able to transparently retrieve the actual IP address of the user in your views in the standard way, by referencing request.META['REMOTE_ADDR']. This approach is preferable if you later decide to remove Cloudflare for some reason - no code changes are required.