---
title: "X-Forwarded-For em Zig: IP Real do Cliente e Proxy Confiável"
url: "https://ziglang.com.br/artigos/zig-http-x-forwarded-for-proxy-confiavel/"
markdown_url: "https://ziglang.com.br/artigos/zig-http-x-forwarded-for-proxy-confiavel.MD"
description: "Como obter o IP real do cliente em Zig atrás de Nginx ou CDN com X-Forwarded-For, X-Real-IP, CF-Connecting-IP, lista de proxies confiáveis e rate limit seguro."
date: "2026-08-18"
author: ""
---

# X-Forwarded-For em Zig: IP Real do Cliente e Proxy Confiável

Como obter o IP real do cliente em Zig atrás de Nginx ou CDN com X-Forwarded-For, X-Real-IP, CF-Connecting-IP, lista de proxies confiáveis e rate limit seguro.


Para obter o **IP real do cliente em um serviço Zig atrás de proxy**, nunca leia `X-Forwarded-For` de forma cega. Aceite headers de encaminhamento **somente** quando o peer TCP imediato estiver em uma lista de proxies confiáveis. Depois, escolha o hop correto da cadeia — descontando à direita os proxies que você controla — e use esse valor para logs, rate limit, geolocalização e auditoria. Se o peer não for confiável, ignore o header e use o endereço do socket remoto.

Esse detalhe parece operacional, mas decide se o [rate limiting](/artigos/zig-rate-limiting-token-bucket/) funciona, se o [Nginx como proxy reverso](/artigos/zig-nginx-proxy-reverso-load-balancing/) entrega telemetria correta e se incidentes de abuso apontam para a origem certa. Em APIs com [cookies e sessões](/artigos/zig-cookies-sessao-http/), o IP também aparece em trilhas de login e em alertas de sessão suspeita.

Como a API de `std.http` pode mudar antes do Zig 1.0, confira `zig version` e a documentação do release instalado. Os nomes exatos de request, headers e peer address podem variar; as regras de confiança e parsing permanecem as mesmas.

## Resposta rápida

| Situação | O que fazer |
|---|---|
| Serviço exposto direto na internet | use o IP do socket remoto |
| Nginx / Caddy / HAProxy na frente | confie só no peer da borda |
| Cloudflare na frente | prefira `CF-Connecting-IP` ou política Cloudflare documentada |
| Header sem peer confiável | ignore e use o socket remoto |
| Vários IPs em `X-Forwarded-For` | aplique uma regra explícita de hop |
| Rate limit / ban | derive a chave do IP confiável |
| Logs de incidente | grave peer TCP + IP derivado |
| `X-Forwarded-Proto` / `Host` | use para URL externa e redirects |
| Cliente forja header | não deve mudar a decisão |
| Teste | simule peer confiável e peer externo |

Se você ainda está montando o serviço, comece pelo guia de [servidor HTTP em Zig para produção](/artigos/zig-http-server-producao/) e configure a borda com o [proxy reverso e load balancing](/artigos/zig-nginx-proxy-reverso-load-balancing/).

## Por que o socket remoto não basta

Quando o cliente fala direto com o binário Zig, o endereço do peer TCP é o cliente. Depois que um proxy entra no caminho, o peer passa a ser o proxy:

```text
navegador 203.0.113.50
    → Cloudflare / Nginx 10.0.0.2
        → serviço Zig :8080
```

Do ponto de vista do processo Zig, toda requisição parece vir de `10.0.0.2`. Sem uma política de encaminhamento:

- o rate limit agrupa o mundo inteiro no IP do proxy;
- banimentos bloqueiam a borda em vez do abusador;
- logs de autenticação apontam para a infraestrutura;
- regras de geofencing e allowlist quebram;
- alertas de “novo IP de login” disparam o tempo todo.

Os proxies preenchem headers para recuperar o cliente original. O problema é que **qualquer cliente também pode enviar esses headers**. Confiança sem autenticação do peer vira buraco de segurança.

## Headers comuns e o que significam

### `X-Forwarded-For`

Formato típico:

```http
X-Forwarded-For: 203.0.113.50, 198.51.100.7, 10.0.0.2
```

Por convenção, a lista cresce da esquerda para a direita: o primeiro valor costuma ser o cliente original e os seguintes são proxies intermediários. Na prática, o cliente pode ter prefixado valores falsos antes de a sua borda acrescentar o hop real. Por isso a regra segura é: **só confie nos hops adicionados depois que o tráfego entrou na sua infraestrutura**.

### `X-Real-IP`

Muitos setups Nginx enviam um único IP:

```http
X-Real-IP: 203.0.113.50
```

É mais simples de parsear, mas continua sendo um header forjável se o serviço aceitar conexões de qualquer origem. Use-o apenas com peer confiável.

### `CF-Connecting-IP`

Em Cloudflare, este header carrega o cliente visto pela rede da Cloudflare. Continua válido o mesmo princípio: aceite-o somente de IPs Cloudflare (ou do seu proxy que já validou a origem).

### `X-Forwarded-Proto` e `X-Forwarded-Host`

Não identificam o cliente, mas restauram o esquema e o host externos:

```http
X-Forwarded-Proto: https
X-Forwarded-Host: api.exemplo.com.br
```

Sem eles, redirects, cookies `Secure`, links absolutos e checagens de CSRF/`Origin` podem assumir `http://127.0.0.1:8080` em vez do domínio público. Veja também [redirects HTTP seguros](/artigos/zig-http-redirecionamentos-301-302-307-308/) e [CORS em APIs Zig](/artigos/zig-cors-api-http/).

## Modele a política antes do parser

Separe três conceitos:

1. **peer TCP** — endereço da conexão imediata;
2. **proxy confiável** — peer autorizado a fornecer headers de encaminhamento;
3. **cliente derivado** — resultado da política aplicada aos headers.

```zig
const std = @import("std");
const net = std.net;

const ClientIdentity = struct {
    peer: net.Address,
    client: net.Address,
    source: enum { socket, x_real_ip, x_forwarded_for, cf_connecting_ip },
    forwarded_chain_len: usize = 0,
};

const TrustError = error{
    UntrustedPeer,
    InvalidHeader,
    EmptyHeader,
    TooManyHops,
    UnsupportedAddress,
};
```

Essa separação evita misturar parsing de string com decisão de segurança. O handler de negócio deve receber um `ClientIdentity` já resolvido, nunca o header cru.

## Checklist de confiança do peer

Antes de olhar qualquer header:

1. obtenha o endereço remoto do socket aceito;
2. compare com a lista de CIDRs/IPs de proxies confiáveis;
3. se não houver match, retorne identidade baseada só no socket;
4. se houver match, aí sim leia os headers permitidos pela política.

Exemplos de peers confiáveis:

- `127.0.0.1` / `::1` quando o proxy roda na mesma máquina;
- rede privada do cluster (`10.0.0.0/8`, `192.168.0.0/16`, etc.) quando só a malha interna alcança o serviço;
- IPs públicos do load balancer ou da Cloudflare, atualizados por processo operacional.

Não hardcode uma allowlist incompleta em produção sem revisão. Prefira configuração explícita por ambiente (`TRUSTED_PROXIES=10.0.0.2/32,10.0.0.3/32`) e falhe fechado: peer desconhecido ⇒ sem confiança nos headers.

## Parsing seguro de `X-Forwarded-For`

Um parser inicial precisa:

- rejeitar header vazio;
- dividir por vírgula;
- fazer trim de espaços;
- limitar o número de hops;
- validar cada token como IPv4 ou IPv6;
- rejeitar lixo (`unknown`, aspas, portas inesperadas, texto livre).

Esboço de abordagem:

```zig
fn parseForwardedFor(
    allocator: std.mem.Allocator,
    header_value: []const u8,
    max_hops: usize,
) TrustError![]net.Address {
    if (header_value.len == 0) return TrustError.EmptyHeader;

    var list: std.ArrayList(net.Address) = .{};
    errdefer list.deinit(allocator);

    var it = std.mem.splitScalar(u8, header_value, ',');
    while (it.next()) |raw_part| {
        const part = std.mem.trim(u8, raw_part, " \t");
        if (part.len == 0) return TrustError.InvalidHeader;
        if (list.items.len >= max_hops) return TrustError.TooManyHops;

        const addr = net.Address.parseIp(part, 0) catch return TrustError.InvalidHeader;
        try list.append(allocator, addr);
    }

    if (list.items.len == 0) return TrustError.EmptyHeader;
    return try list.toOwnedSlice(allocator);
}
```

Ajuste `parseIp` e a representação de porta conforme a stdlib do seu release. O ponto importante é **falhar em input ambíguo** em vez de “melhor esforço”. Rate limit e banimentos não admitem IP inventado por parser permissivo.

## Qual hop escolher

Há duas políticas comuns:

### 1. Borda única + `X-Real-IP`

Quando só o seu Nginx fala com o Zig:

```nginx
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;
proxy_set_header Host $host;
```

No Zig, com peer confiável, leia `X-Real-IP` e ignore a cadeia completa. É o desenho mais simples e menos sujeito a spoofing do prefixo.

### 2. Cadeia com hops confiáveis conhecidos

Se existem N proxies seus, conte hops a partir da direita (próximos do seu serviço) e descarte somente os hops que sua infraestrutura adicionou. O próximo endereço à esquerda dessa zona confiável é o cliente.

Exemplo:

```text
X-Forwarded-For: 198.51.100.9, 203.0.113.50, 10.0.0.2
peer TCP: 10.0.0.2 (confiável)
```

Se a política diz “um hop interno”, o cliente derivado é `203.0.113.50`. O valor `198.51.100.9` pode ter sido prefixado pelo próprio cliente e não deve mandar na decisão.

**Nunca** use “primeiro da esquerda” sem modelar hops confiáveis. Essa heurística é exatamente o que um atacante explora.

## Implementação de resolução

```zig
fn resolveClient(
    peer: net.Address,
    trusted: []const Cidr,
    headers: anytype,
    max_hops: usize,
    allocator: std.mem.Allocator,
) TrustError!ClientIdentity {
    if (!isTrustedPeer(peer, trusted)) {
        return .{
            .peer = peer,
            .client = peer,
            .source = .socket,
        };
    }

    if (headers.get("cf-connecting-ip")) |cf| {
        const addr = net.Address.parseIp(std.mem.trim(u8, cf, " \t"), 0) catch {
            return TrustError.InvalidHeader;
        };
        return .{
            .peer = peer,
            .client = addr,
            .source = .cf_connecting_ip,
            .forwarded_chain_len = 1,
        };
    }

    if (headers.get("x-real-ip")) |real| {
        const addr = net.Address.parseIp(std.mem.trim(u8, real, " \t"), 0) catch {
            return TrustError.InvalidHeader;
        };
        return .{
            .peer = peer,
            .client = addr,
            .source = .x_real_ip,
            .forwarded_chain_len = 1,
        };
    }

    if (headers.get("x-forwarded-for")) |xff| {
        const chain = try parseForwardedFor(allocator, xff, max_hops);
        defer allocator.free(chain);
        // Borda única com $proxy_add_x_forwarded_for: o Nginx acrescenta
        // $remote_addr à direita. O cliente visto pela borda é o último hop.
        // Valores à esquerda podem ter sido prefixados pelo próprio cliente.
        // Se houver mais de um proxy interno, desconte hops confiáveis à direita
        // e só então leia o próximo endereço. Prefira X-Real-IP quando puder.
        const client = chain[chain.len - 1];
        return .{
            .peer = peer,
            .client = client,
            .source = .x_forwarded_for,
            .forwarded_chain_len = chain.len,
        };
    }

    return .{
        .peer = peer,
        .client = peer,
        .source = .socket,
    };
}
```

O comentário no trecho de `X-Forwarded-For` é proposital: **a escolha do índice depende da topologia**. Em muitos times brasileiros a combinação mais segura é peer confiável + `X-Real-IP`, deixando `X-Forwarded-For` só para auditoria.

## Integração com rate limit e ban

O [token bucket por IP](/artigos/zig-rate-limiting-token-bucket/) só é justo se a chave for o cliente derivado:

```zig
const key = try std.fmt.allocPrint(allocator, "{f}", .{identity.client});
defer allocator.free(key);
try limiter.consume(key, 1);
```

Regras práticas:

- não misture peer e cliente na mesma chave;
- se a resolução falhar por header inválido vindo de peer confiável, trate como erro de configuração (métrica + log), não como “IP desconhecido liberado”;
- considere IPv6 e prefixos `/64` se for limitar redes, não apenas hosts;
- em login e recuperação de senha, combine IP derivado com outros sinais (conta, device, cookie) para não punir CGNAT demais.

## Logs, métricas e observabilidade

Grave campos distintos:

```text
peer_ip=10.0.0.2 client_ip=203.0.113.50 client_source=x_real_ip xff_hops=2
```

Isso permite:

- detectar queda súbita de `client_source=socket` (proxy deixou de enviar header);
- auditar spoofing quando `xff_hops` explode;
- correlacionar com [health checks](/artigos/zig-health-check-readiness-liveness/) e [observabilidade](/artigos/zig-observabilidade-logs-prometheus/).

Métricas úteis:

- `http_client_identity_source{source="socket|x_real_ip|xff|cf"}`
- `http_trusted_proxy_requests_total`
- `http_invalid_forwarded_header_total`
- `http_rate_limit_key_ip_family{family="ipv4|ipv6"}`

## Scheme e host externos

Além do IP, normalize a URL pública:

```zig
const ForwardedContext = struct {
    scheme: []const u8, // "https" ou "http"
    host: []const u8,
};
```

Política sugerida com peer confiável:

1. `X-Forwarded-Proto` se for `http` ou `https`;
2. senão, scheme da conexão TLS local;
3. `X-Forwarded-Host` ou `Host`, com allowlist de hosts do produto;
4. rejeite hosts inesperados para bloquear cache poisoning e redirects abertos.

Essa base alimenta Location em redirects, links em e-mails, cookies `Secure` e checagens de origem. Combine com [TLS/HTTPS e mTLS](/artigos/zig-tls-https-certificados-mtls/) quando o Zig termina TLS diretamente.

## Testes que precisam passar

Cubra pelo menos:

| Caso | Esperado |
|---|---|
| peer não confiável + `X-Forwarded-For` forjado | cliente = peer |
| peer confiável + `X-Real-IP` válido | cliente = header |
| peer confiável + `X-Real-IP` inválido | erro / métrica de config |
| peer confiável sem headers | cliente = peer |
| `X-Forwarded-For` com hops demais | rejeição |
| IPv6 com zona/porta inesperada | rejeição ou política explícita |
| Cloudflare header com peer não-CF | ignore header |
| rate limit usa cliente derivado | abusador isolado, proxy não banido |

Teste de integração com `curl` atrás de um Nginx local:

```bash
# peer aparente = nginx; cliente derivado deve ser o IP que o nginx viu
curl -i https://api.local.exemplo/health
```

E um teste negativo apontando direto no serviço:

```bash
curl -i \
  -H 'X-Forwarded-For: 198.51.100.9' \
  -H 'X-Real-IP: 198.51.100.9' \
  http://127.0.0.1:8080/debug/whoami
```

Se o processo Zig estiver configurado para confiar só no Nginx da rede interna, a resposta de `whoami` deve mostrar o seu IP de socket, não `198.51.100.9`.

## Armadilhas frequentes

1. **Confiar no header sem olhar o peer** — o erro clássico; permite bypass de ban e rate limit.
2. **Usar sempre o primeiro IP da esquerda** — o cliente escolhe esse valor.
3. **Allowlist de proxy desatualizada** — depois de migrar load balancer, tudo cai para `source=socket` ou, pior, para confiança acidental.
4. **Tratar `unknown` como IP** — alguns proxies legados emitem tokens não parseáveis; rejeite.
5. **Esquecer IPv6** — parsers só IPv4 quebram silenciosamente atrás de dual-stack.
6. **Logar só o cliente derivado** — sem o peer, você não diagnostica spoofing.
7. **Aplicar a mesma política em ambientes locais e produção** — em dev, confiar em `127.0.0.1` é ok; em produção, documente CIDRs reais.
8. **Misturar CDN e origem sem mTLS/rede privada** — se qualquer host alcançar a origem, a allowlist de proxy vaza.

## Checklist antes do deploy

- [ ] O serviço não é alcançável publicamente exceto via proxy/CDN esperados.
- [ ] Existe lista explícita de peers/CIDRs confiáveis por ambiente.
- [ ] Headers de encaminhamento são ignorados para peers fora da lista.
- [ ] A escolha entre `X-Real-IP`, `X-Forwarded-For` e `CF-Connecting-IP` está documentada.
- [ ] Parser valida IP e limita hops.
- [ ] Rate limit, ban e auditoria usam o cliente derivado.
- [ ] Logs incluem `peer_ip`, `client_ip` e `client_source`.
- [ ] `X-Forwarded-Proto`/`Host` alimentam redirects e cookies com allowlist.
- [ ] Testes cobrem spoofing direto na origem.
- [ ] Métricas alertam header inválido e queda de confiança.

## Perguntas frequentes

### Posso confiar no X-Forwarded-For enviado por qualquer cliente?

Não. Qualquer cliente pode inventar `X-Forwarded-For`. Só use esse header quando a conexão TCP imediata vier de um proxy ou load balancer que você controla e confia. Caso contrário, use o endereço do socket remoto.

### Qual header preferir: X-Forwarded-For, X-Real-IP ou CF-Connecting-IP?

Depende da borda. Em Nginx típico, `X-Real-IP` ou o hop confiável de `X-Forwarded-For`. Em Cloudflare, `CF-Connecting-IP` costuma ser o mais direto. Escolha uma política explícita e documente a cadeia esperada.

### Como X-Forwarded-For afeta rate limiting em Zig?

Se o limitador usar o IP errado, um atacante pode forjar o header e escapar do limite, ou vários usuários legítimos atrás do mesmo NAT/CDN podem ser bloqueados juntos. Derive a chave somente de um hop confiável.

### Devo registrar o socket remoto ou o IP encaminhado?

Registre os dois quando houver proxy: o endereço do peer TCP e o cliente derivado da política. Isso facilita auditar spoofing, erros de configuração e incidentes de abuso.

### X-Forwarded-For resolve sozinho redirects HTTPS e CSRF?

Não. Ele ajuda a recuperar o cliente e, com `X-Forwarded-Proto`/`Host`, o esquema e o host externos. Redirects, cookies `Secure` e CSRF ainda precisam de política própria.

## Conclusão

Em Zig, descobrir o IP do cliente atrás de proxy é um problema de **confiança**, não de parsing. Comece pelo peer TCP, aceite headers apenas de proxies autorizados, escolha o hop com uma regra documentada e propague essa identidade para logs, rate limit e auditoria. Prefira desenhos simples — borda única com `X-Real-IP` — antes de cadeias longas de `X-Forwarded-For`.

Esse cuidado fecha um buraco clássico em serviços que já usam Nginx, CDN e limitadores. Combine este guia com [servidor HTTP de produção](/artigos/zig-http-server-producao/), [proxy reverso](/artigos/zig-nginx-proxy-reverso-load-balancing/) e [rate limiting](/artigos/zig-rate-limiting-token-bucket/) para manter abuso, observabilidade e redirects alinhados ao mundo real fora do container.
