O que fica entre o navegador e sua app

Aponte o navegador para example.com e a requisição nunca toca diretamente no processo que executa seu código. Ela encontra primeiro outra coisa: um servidor que lê a requisição, decide qual backend deve responder, a encaminha para lá e devolve a resposta como se a tivesse produzido ele mesmo. Esse servidor é um reverse proxy.

Nginx, Caddy, Traefik e HAProxy fazem esse trabalho, assim como o load balancer que fica na frente de um cluster Kubernetes. O esquema nunca muda: os clientes falam com o proxy e com mais nada. Os servidores reais ficam atrás, em qualquer quantidade e onde quer que rodem.

Como um reverse proxy trata uma requisição

Um cliente abre uma conexão com o IP e a porta do reverse proxy, do mesmo jeito que abriria com qualquer servidor. O proxy termina essa conexão, lê a linha da requisição e os headers, e os compara com suas próprias regras — geralmente o header Host e o caminho da URL. Com base nesse resultado, ele abre uma segunda conexão, separada, com um backend, encaminha a requisição por ela, espera a resposta e a repassa de volta pela conexão original do cliente.

Duas conexões independentes, costuradas uma à outra. Do lado do cliente, isso é indistinguível de falar diretamente com o backend. Do lado do backend, cada requisição parece vir de uma única máquina, o proxy, em vez de vir da internet como um todo.

1
2
cliente  --- requisição --->  reverse proxy  --- requisição --->  servidor backend
cliente  <-- resposta ---  reverse proxy  <-- resposta ---  servidor backend

Como o proxy vê cada requisição antes do backend, ele pode inspecionar, modificar, cachear ou rejeitar qualquer coisa que passe por ali. É essa posição que faz o proxy herdar o trabalho que ninguém quer dentro da aplicação: encerrar o TLS, comprimir respostas, cachear conteúdo estático, limitar clientes abusivos e direcionar caminhos diferentes para serviços diferentes — /api para um backend, / para outro, /static direto do disco.

Reverse proxy vs forward proxy

Os dois ficam no meio de uma conexão. A diferença está em para quem eles trabalham.

Forward proxyReverse proxy
Age em nome deO clienteO servidor
O cliente sabe que existeGeralmente sim (configurado explicitamente)Não (parece o servidor real)
O que ele escondeA identidade do cliente do servidorA identidade do servidor do cliente
Uso típicoFiltragem de saída corporativa, contornar geobloqueios, navegação anônimaRoteamento, terminação TLS, cache, proteção dos servidores de origem

Um forward proxy é a caixa que sua empresa coloca no escritório para que todo o tráfego de saída passe por um único ponto filtrado: o site que você visita registra o endereço do proxy, não o do seu notebook. Um reverse proxy é operado por quem é dono do site, e esconde a outra ponta. De fora, não dá para saber se example.com é um servidor ou quarenta.

Reverse proxy vs load balancer

Os dois termos são usados como sinônimos, principalmente porque um mesmo processo Nginx ou HAProxy costuma fazer os dois trabalhos ao mesmo tempo.

Reverse proxyLoad balancer
Pergunta central que respondeO que deve acontecer com esta requisição?Qual servidor deve tratar esta requisição?
Funciona com um único backend?Sim, continua útil (TLS, cache, roteamento por caminho)Não, precisa de pelo menos dois para balancear
Recursos extras típicosCache, reescrita de headers, terminação TLS, roteamento por caminhoHealth checks, distribuição ponderada, afinidade de sessão

Um reverse proxy na frente de um único backend ainda se justifica: ele termina o TLS para que sua app não precise fazer isso, esconde o endereço real do backend, adiciona gzip. Um load balancer não tem nada a fazer enquanto não houver pelo menos dois backends para escolher. O rótulo que você usa descreve a configuração que você escreveu, não o software que instalou.

Como configurar o Nginx como reverse proxy

Escutar em uma porta, comparar um hostname, encaminhar para um backend. Isso já é o mínimo necessário:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $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;
    }
}

proxy_pass é a linha que importa: tudo o que location captura vai para http://127.0.0.1:3000. As quatro linhas proxy_set_header existem porque, sem elas, o backend vê o proxy como cliente, e cada requisição parece vir de 127.0.0.1. X-Real-IP e X-Forwarded-For carregam o endereço real do cliente até o fim. X-Forwarded-Proto diz ao backend se a requisição original era HTTPS, já que o TLS foi terminado no proxy e o próximo salto é HTTP puro.

Um detalhe quebra configurações o tempo todo: uma barra final na URI do proxy_pass muda o caminho encaminhado. proxy_pass http://backend; (sem caminho) encaminha a URI original sem alterações. proxy_pass http://backend/; (com barra final) remove, antes de encaminhar, a parte da URI que location capturou. Com location /api/ e proxy_pass http://backend/;, uma requisição para /api/users chega ao backend como /users. Inverta isso e todas as rotas retornam 404 no backend, mesmo que um curl direto no proxy pareça funcionar normalmente.

Reverse proxy na frente de uma app em Docker

É aqui que esse padrão aparece mais no dia a dia: Nginx em um container, sua app em outro, ambos na mesma rede Docker para que o Nginx alcance a app pelo nome do container — o comportamento de DNS descrito em o que uma rede Docker oferece.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# nginx.conf
server {
    listen 80;

    location / {
        proxy_pass http://app:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# compose.yaml
services:
  app:
    build: .
    expose:
      - "3000"

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app

app expõe a porta 3000 na rede do Compose, mas não publica nada no host. Só o Nginx faz isso, na porta 80. O nome app:3000 na configuração do Nginx é resolvido pelo DNS interno do Docker, o mesmo mecanismo descrito no guia do Docker Compose. Rode docker compose up e as requisições para localhost chegam ao Nginx, que as encaminha para o container da app. A app nunca fica acessível sozinha de fora do host, o que é tão intencional quanto o próprio roteamento.

O que um reverse proxy custa

Ele adiciona um salto de rede e mais um lugar onde uma requisição pode falhar. Um proxy_pass mal configurado ou um header perdido aparece como bug na app quando a falha está uma camada acima. Também é um ponto único de falha por padrão: derrube esse único processo Nginx e cada backend atrás dele fica inacessível. Ambientes de produção ou o rodam de forma redundante, ou passam a tarefa para algo que já assume a falha como certa — um load balancer na nuvem, ou um Ingress controller do Kubernetes apoiado nos Deployments e Pods por trás dele.

Um reverse proxy também não torna uma app mais rápida ou mais escalável sozinho. Ele pode cachear e comprimir, mas um backend lento continua lento; o proxy só encaminha essa lentidão um salto depois. E não é autenticação nem validação de entrada. Bloquear requisições claramente malformadas na borda é um bônus, nunca um motivo para a app parar de checar o que recebe.

Levando essa configuração para produção

Pegue o compose.yaml acima, troque app por um serviço real, e adicione ssl_certificate e ssl_certificate_key ao bloco server do Nginx assim que tiver um certificado. Essa é a diferença entre um exercício local e algo que você colocaria na frente de tráfego real. Se você rotear para mais de um backend, troque o endereço único por um bloco upstream com várias linhas server e aponte o proxy_pass para esse bloco: a mesma configuração vira um load balancer.

Artigos relacionados