Como obter o IP real do cliente em PHP usando Cloudflare Tunnel

Se você usa Cloudflare com Tunnel (Argo Tunnel / cloudflared), esquece $_SERVER['REMOTE_ADDR'].
Ele vai trazer 127.0.0.1 ou IP interno do túnel. Inútil.

O IP real vem via headers.

O problema

Com Cloudflare Tunnel, o fluxo é:

Cliente → Cloudflare → Tunnel → Seu servidor

Ou seja, quem conecta no seu PHP não é o cliente, é o proxy da Cloudflare.

Resultado:

echo $_SERVER['REMOTE_ADDR']; 
// 127.0.0.1 (ou IP interno)

Errado.


Headers corretos

A Cloudflare injeta alguns headers úteis:

  • HTTP_CF_CONNECTING_IP → IP real do cliente (o que você quer)
  • HTTP_X_FORWARDED_FOR → pode ter múltiplos IPs (cadeia de proxies)
  • HTTP_X_REAL_IP → nem sempre presente

Solução simples e objetiva

function getRealIP()
{
    if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
        return $_SERVER['HTTP_CF_CONNECTING_IP'];
    }

    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        // Pode vir "IP1, IP2, IP3"
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($ips[0]);
    }

    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
        return $_SERVER['HTTP_X_REAL_IP'];
    }

    return $_SERVER['REMOTE_ADDR'];
}

echo getRealIP();

Versão paranoica (validação)

Porque header pode ser spoofado fora do Cloudflare:

function getRealIP()
{
    $ip = null;

    if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
        $ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $ip = trim($ips[0]);
    } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
        $ip = $_SERVER['HTTP_X_REAL_IP'];
    } else {
        $ip = $_SERVER['REMOTE_ADDR'];
    }

    return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : null;
}

Segurança (importante)

Se teu servidor estiver exposto direto (sem firewall), alguém pode mandar:

X-Forwarded-For: 1.2.3.4

E pronto, te enganou.

Regra: só confie nesses headers se:

  • O acesso vier exclusivamente via Cloudflare
  • Ou você validar o IP de origem contra ranges da Cloudflare

Dica de produção (melhor abordagem)

Trave acesso direto:

  • Firewall liberando apenas IPs da Cloudflare
  • Ou rode o serviço só via localhost (Tunnel já faz isso bem)

Assim:

Qualquer header ≈ confiável

Debug rápido

print_r($_SERVER);

Vai ver exatamente o que o Tunnel está entregando.


TL;DR

  • REMOTE_ADDR = lixo em ambiente com tunnel
  • Use HTTP_CF_CONNECTING_IP
  • Fallback para X_FORWARDED_FOR
  • Valide IP
  • Restrinja acesso ao servidor

Rolar para cima