Configuraste tu nueva instalación de Nginx con Django detrás de Cloudflare y tu página es públicamente accesible desde Internet, todo parece funcionar correctamente, pero luego instalas una aplicación para integrar pagos en su sitio web como django-paypal, la cual depende de la IP desde donde se originó la solicitud a tu servidor y esta aplicación no funciona, o simplemente necesitas la IP real para hacer validaciones o llevar un registro y lo que obtienes es la IP de algún servidor proxy de Cloudflare ¿Por qué sucede esto?

En una explicación muy breve: Cloudflare funciona como un reverse proxy server, es decir, recibe el tráfico de nuestros usuarios, los procesa y por último, lo redirige a nuestro servidor web NGINX. Una vez en nuestro servidor, la solicitud es redirigida a nuestra instancia de Django (Gunicorn). Uno de los beneficios de utilizar Cloudflare de por medio, es que enmascara la IP real de nuestro servidor web a nuestros clientes, por ende incrementa la seguridad.

Cuando Cloudflare procesa las solicitudes, reemplaza ciertos headers, por ejemplo, la IP con información propia del servidor proxy que recibió la solicitud y la información de origen pasa a ser otro header con otro nombre, por supuesto.

Es por ello que sin ninguna configuración post-instalación, no podemos obtener la información de origen y para ello vamos a revisar ciertos cambios que debemos hacer en nuestro servidor NGINX, para obtener la información correcta y pasarla correctamente a nuestra instancia de Django.

Básicamente son tres pasos los que vamos a realizar para solucionar el problema:

  • Establecer la IP real del cliente en nuestro servidor web NGINX.
  • Establecer el header HTTP_X_REAL_IP y enviarlo a nuestra instancia de Django.
  • Reemplazar la información del header REMOTE_ADDR por HTTP_X_REAL_IP en nuestra instancia de Django.

Antes de continuar, podemos evidenciar el problema de la IP real en los logs de acceso de NGINX, el primer argumento es la IP y en este punto debería pertenecer a algún servidor proxy de Cloudflare.

172.68.65.112 - - [04/Jan/2020:18:05:47 +0000] "GET /app/ HTTP/1.1" 200 3792 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
/var/log/nginx/access.log

En mi caso, 127.68.65.112 efectivamente pertenece a una IP de Cloudflare.

Lo principal, es definir una lista de direcciones IP de servidores proxy de Cloudflare en un nuevo archivo de configuración, las cuales NGINX considerará de confianza. Además, también es necesario indicar cuál es el header que contiene la IP real.

Puedes leer más sobre estas dos directivas en set_real_ip_from y real_ip_header

set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;

# utiliza cualquiera de los dos parametros de configuracion siguientes
real_ip_header CF-Connecting-IP;
/etc/nginx/cloudflare

En el momento de la publicación el ejemplo de la lista de direcciones IP de servidores proxy de Cloudflare estaba actualizado, por lo que se recomienda siempre verificar la lista de rango de direcciones IP y actualizar la lista en caso de ser necesario.

Lo siguiente, es indicar en la configuración de Cloudflare que se lea este nuevo archivo de configuración.

http {
    ...
    
    ##
    # CloudFlare
    ##
    include /etc/nginx/cloudflare;
    
    ...
}
/etc/nginx/nginx.conf

Es pertinente verificar que NGINX puede leer esta nueva configuración de manera correcta, por lo que recomiendo ejecutar el comando: sudo nginx -t

Si todo está correcto, deberías ver lo siguiente:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Volvamos a revisar los logs de acceso de NGINX después del cambio anterior.

190.25.***.*** - - [04/Jan/2020:17:47:18 +0000] "GET /app/ HTTP/1.1" 200 3800 "https://gearlytics.ai/login/?next=/app/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
/var/log/nginx/access.log

La IP 190.25.***.*** pertenece a la IP real del usuario, por lo que el cambio fue exitoso.

Ahora, vamos a indicar los headers a enviar a nuestra instancia de Django en un nuevo archivo de configuración y aprovechar para enviar la IP real.

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
/etc/nginx/proxy_params

En el archivo de configuración de nuestro sitio, se debe indicar que lea el nuevo archivo de configuración.

server {
    ...
    
    location / {
        include proxy_params;
        ...
    }
    
    ...
}
/etc/nginx/sites-available/website

Nuevamente ejecutamos el comando de prueba: sudo nginx -t

Y esperamos nuevamente volver a ver el mensaje de configuración correcta:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Lo único que queda es reiniciar el servidor NGINX: sudo systemctl restart nginx

En este punto ya debería estar bien configurado las directivas de NGINX para enviar la información correcta de IP a nuestra instancia de Django. Lo podemos confirmar imprimiendo en nuestra instancia de Django self.request.META en cualquier view.

...
'REMOTE_ADDR': '127.0.0.1',
'HTTP_HOST': 'migueldar.io',
'HTTP_X_REAL_IP': '190.25.***.***',
'HTTP_X_FORWARDED_FOR': '190.25.***.***, 190.25.***.***',
'HTTP_X_FORWARDED_PROTO': 'https',
...

Por último, debemos reemplazar el header REMOTE_ADDR por HTTP_X_REAL_IP que ahora estamos enviando. Para esto vamos a agregar un mixin en Django.

MIDDLEWARE = [
    ...
    'utils.middleware.XRealIPMiddleware',
]
settings.py

from django.utils.deprecation import MiddlewareMixin


class XRealIPMiddleware(MiddlewareMixin):
    """Replaces REMOTE_ADDR with the value X-Real-IP header"""

    def process_request(self, request):
        try:
            real_ip = request.META['HTTP_X_REAL_IP']
        except KeyError:
            pass
        else:
            request.META['REMOTE_ADDR'] = real_ip
~/your_project/utils/middleware/XRealIPMiddleware.py

Reiniciamos la instancia de Django y listo, ya tenemos la IP real en el header REMOTE_ADDR.

Esta configuración post-instalación es muy útil por lo que cualquier aplicación de Django que haga uso de este header, funcionará ahora correctamente.

Espero te haya servido de mucha ayuda esta publicación.