La caza de la IP: ¿por qué $_SERVER['REMOTE_ADDR'] ya no es suficiente?
\n\nImagina que lanzas un proyecto educativo increíble en PHP. Todo funciona, los usuarios están contentos. Pero decides añadir registro de visitantes, geolocalización o, por ejemplo, protección contra votos fraudulentos. Y de repente — ¡pum! — descubres que la mitad de tus visitantes "únicos" tienen la misma IP: 127.0.0.1 o 10.0.0.1. ¿Te suena?
El problema de obtener la IP real del cliente en PHP es un clásico que nunca pasa de moda. La pregunta en Stack Overflow acumula miles de visitas, y tanto principiantes como expertos siguen cayendo en los mismos errores. En 2025, con Cloudflare, AWS, Nginx y un montón de proxies CDN como estándar, confiar solo en $_SERVER['REMOTE_ADDR'] es como leer el futuro en posos de café.
En este artículo no solo te daremos una función lista para usar. Analizaremos la anatomía de las cabeceras HTTP, aprenderemos a distinguir aliados de enemigos y crearemos un método seguro y fiable para obtener la IP del visitante en PHP que no te fallará en 2025. ¡Allá vamos!
\n\nREMOTE_ADDR: tu fiel pero ciego compañero
\n\nEmpecemos por lo básico. $_SERVER['REMOTE_ADDR'] es la IP de quien se conecta directamente a tu servidor web. Si tu sitio está en un servidor dedicado sin proxy, esa es la IP del cliente. Simple y fiable.
Pero en el mundo actual, esto es raro. Normalmente, entre el usuario y tu script PHP hay toda una cadena:
\n- \n
- CDN (Cloudflare, Fastly) \n
- Balanceador de carga (Nginx, HAProxy) \n
- Proxy inverso (Varnish, Nginx) \n
- Hosting con arquitectura compartida \n
Y aquí REMOTE_ADDR muestra la IP del último eslabón (por ejemplo, tu balanceador), no la del usuario real. Para un sitio educativo, esto es un desastre: no podrás distinguir a un estudiante de Madrid de uno de Buenos Aires, y tu sistema antispam bloqueará a todos por igual.
Cabeceras HTTP: ¿dónde se esconde la IP real?
\n\nPara transmitir la IP real del cliente, los servidores proxy añaden cabeceras HTTP especiales. Las más populares en 2025 son:
\n\n1. X-Forwarded-For (XFF)
\nLa cabecera clásica. Puede contener una cadena de IPs: client_ip, proxy1_ip, proxy2_ip. La primera IP de la lista es el cliente real. Pero hay un detalle: cualquier atacante puede falsificar esta cabecera si tu infraestructura no la limpia.
2. X-Real-IP
\nLa cabecera favorita de Nginx. Contiene solo una IP: la real. Más simple y segura, pero no todos los proxies la soportan.
\n\n3. CF-Connecting-IP (Cloudflare)
\nCabecera específica de Cloudflare. Si usas su CDN, es la opción más fiable. Pero solo si confías en Cloudflare (y deberías, son excelentes).
\n\n\n Importante: Nunca confíes en las cabeceras\n\nX-Forwarded-ForyX-Real-IPde fuentes desconocidas. Un atacante puede inyectar cualquier IP y tu sistema la considerará real.\n
Mejor práctica 2025: protección en capas
\n\n¿Entonces qué hacemos? No podemos simplemente tomar la primera IP de X-Forwarded-For — es peligroso. Pero tampoco podemos ignorar los proxies. La solución es una lista blanca de proxies de confianza.
La idea es simple: conocemos las IPs de nuestros balanceadores y CDN. Si la solicitud viene de ellos, confiamos en sus cabeceras. Si no, usamos REMOTE_ADDR.
Aquí tienes una clase funcional en PHP que implementa esta lógica:
\n\n<?php\n\nclass ClientIP\n{\n /**\n * Lista de IPs y subredes de confianza (CIDR).\n * Añade aquí las IPs de tus proxies, CDN y balanceadores.\n */\n private static array $trustedProxies = [\n '127.0.0.1', // localhost\n '10.0.0.0/8', // redes privadas\n '172.16.0.0/12',