Empilhando Proxies Reversos: Uma Arquitetura para Produção
Quatro proxies reversos na frente de uma única aplicação parece, à primeira vista, um sistema que precisa de simplificação. A maioria dos tutoriais sobre infraestrutura web em produção opta por uma única camada — Nginx, Caddy ou Traefik — e trata qualquer coisa a mais como over-engineering. Mas, na prática, cada uma dessas ferramentas resolve um problema distinto, e quando você entende a divisão de responsabilidades, a redundância aparente se resolve em algo coerente.
Esta é a arquitetura que executamos para vários deployments de clientes: Cloudflare na borda, Traefik como ponto de entrada consciente de contêineres, Varnish para cache HTTP, Nginx como host da aplicação e a própria aplicação no final da cadeia. O fluxo de requisições é:
Cloudflare → Traefik → Varnish → Nginx → Application
O que segue explica o que cada camada contribui, onde ficam os limites e quais são as implicações de desempenho — incluindo os pontos onde esta stack cria overhead que uma configuração mais simples não criaria.
O Que Cada Camada Realmente Faz
Cloudflare opera na borda da internet pública. Absorve tráfego DDoS, aplica regras WAF, encerra TLS em PoPs distribuídos globalmente e armazena em cache ativos estáticos próximos aos usuários finais. O que ele não faz bem é tomar decisões inteligentes de roteamento sobre seus contêineres ou armazenar em cache respostas de aplicação que requerem inspeção de cookies. Isso é trabalho de outra ferramenta.
Traefik é o ponto de entrada consciente de contêineres dentro da sua infraestrutura. When you run a stack Docker multi-tenant, O valor do Traefik é que ele lê labels de contêineres e roteia de acordo — sem arquivos de configuração manuais que ficam desatualizados quando os contêineres reiniciam ou escalam. Ele gerencia certificados TLS do Let's Encrypt para domínios internos ou de staging, remove ou reescreve headers e aplica cadeias de middleware como allowlisting de IP ou autenticação básica. Traefik não faz cache. Não comprime bem sob carga. Seu trabalho é roteamento e middleware, e ele faz esse trabalho de forma eficiente.
Varnish é o acelerador HTTP. Ele fica entre o ponto de entrada e o host da aplicação especificamente para absorver tráfego de leitura. Uma instância Varnish bem ajustada pode servir dezenas de milhares de requisições por segundo da memória, retornando páginas HTML completas em menos de um milissegundo. Sua VCL (Varnish Configuration Language) oferece controle preciso sobre o que é armazenado em cache, por quanto tempo e em quais condições — muito mais granular do que qualquer regra de cache do Cloudflare oferece. Isso é importante para aplicações autenticadas onde algumas respostas são cacheáveis por usuário, ou para páginas que devem ter TTLs diferentes dependendo dos parâmetros de consulta.
Nginx, nesta stack, desempenha o papel para o qual foi originalmente projetado: servir arquivos e fazer proxy para um processo de aplicação upstream. CloudPanel usa o Nginx como seu servidor web gerenciado, o que significa que cada vhost tem uma configuração gerada automaticamente que lida com PHP-FPM, serving de ativos estáticos e compressão gzip. Essa configuração é mantida pelo CloudPanel e não deve ser editada diretamente. O Nginx aqui não é uma camada de tomada de decisões — ele recebe uma requisição, faz proxy para a aplicação e retorna a resposta.
Configuração do Traefik: Labels de Contêineres
O roteamento do Traefik para esta stack é configurado inteiramente via labels do Docker Compose. O insight chave ao colocar o Varnish atrás do Traefik é que o Traefik deve encaminhar para o contêiner Varnish, não diretamente para o Nginx. É aqui que a maioria das pessoas se confunde: os exemplos padrão do Traefik mostram um padrão direto para o app, e enxertar o Varnish nesse fluxo não está bem documentado.
services:
varnish:
image: varnish:7.5
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`www.example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls=true"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.services.myapp.loadbalancer.server.port=80"
- "traefik.http.middlewares.myapp-proto.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.myapp-proto.headers.customrequestheaders.X-Forwarded-Port=443"
- "traefik.http.routers.myapp.middlewares=myapp-proto"
networks:
- traefik-public
- internal
O header X-Forwarded-Proto é crítico. Sem ele, sua aplicação vê cada requisição como HTTP — porque a comunicação Varnish-para-Nginx roda sobre HTTP simples na rede interna do Docker — o que causa loops de redirecionamento em qualquer aplicação que aplique HTTPS. O Traefik adiciona o header antes que a requisição entre na rede interna, o Varnish o passa intacto e o Nginx o encaminha para a aplicação. O header complementar X-Forwarded-Port previne a mesma classe de loop de redirecionamento em aplicações que também inspecionam a porta.
Uma segunda consideração: a configuração passHostHeader do Traefik tem como padrão true, o que significa que o Varnish recebe o header Host original. É isso que você quer — a VCL do Varnish pode usá-lo para chaveamento de cache, e o Nginx o usa para seleção de vhost. Se você sobrescrever isso, tanto o Varnish quanto o Nginx receberão o host errado e falharão silenciosamente de formas difíceis de rastrear.
VCL do Varnish: Lógica de Cache
A VCL abaixo é um ponto de partida funcional para uma aplicação WordPress ou PHP atrás do CloudPanel. Ela cobre os casos comuns: bypass para usuários logados e requisições não idempotentes, cache estendido para ativos estáticos, normalização de parâmetros de rastreamento e um período de grace para prevenir cache stampedes.
vcl 4.1;
backend default {
.host = "nginx"; # service name in docker-compose
.port = "80";
.connect_timeout = 5s;
.first_byte_timeout = 30s;
.between_bytes_timeout = 10s;
}
sub vcl_recv {
# Pass admin and authenticated requests
if (req.url ~ "^/wp-(admin|login|cron|json)" ||
req.http.Cookie ~ "wordpress_logged_in_" ||
req.http.Cookie ~ "woocommerce_items_in_cart") {
return(pass);
}
# Only cache GET and HEAD
if (req.method != "GET" && req.method != "HEAD") {
return(pass);
}
# Normalise tracking parameters out of the cache key
if (req.url ~ "(?|&)(utm_source|utm_medium|utm_campaign|fbclid|gclid)=") {
set req.url = regsuball(req.url,
"&(utm_source|utm_medium|utm_campaign|fbclid|gclid)=[^&]+", "");
set req.url = regsuball(req.url,
"?(utm_source|utm_medium|utm_campaign|fbclid|gclid)=[^&]+&", "?");
set req.url = regsub(req.url,
"?(utm_source|utm_medium|utm_campaign|fbclid|gclid)=[^&]+$", "");
}
# Remove cookies for static assets — allows caching
if (req.url ~ ".(css|js|png|jpg|jpeg|gif|ico|woff2?|svg|webp)(?.*)?$") {
unset req.http.Cookie;
}
return(hash);
}
sub vcl_backend_response {
# Cache static assets for 7 days
if (bereq.url ~ ".(css|js|png|jpg|jpeg|gif|ico|woff2?|svg|webp)(?.*)?$") {
set beresp.ttl = 7d;
set beresp.grace = 24h;
unset beresp.http.Set-Cookie;
return(deliver);
}
# Cache HTML pages for 5 minutes with a 60-second grace period
if (beresp.status == 200 && beresp.http.Content-Type ~ "text/html") {
set beresp.ttl = 300s;
set beresp.grace = 60s;
}
# Never cache error responses
if (beresp.status >= 500) {
set beresp.ttl = 0s;
}
return(deliver);
}
sub vcl_deliver {
# Expose cache status for debugging
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
# Remove server identity headers
unset resp.http.Via;
unset resp.http.X-Varnish;
return(deliver);
}
A normalização de parâmetros de rastreamento em vcl_recv é fácil de ignorar e cara de negligenciar. O Cloudflare remove alguns parâmetros na borda, mas valores UTM e tokens fbclid chegam ao Varnish com a URL original intacta. Sem normalização, cada clique em anúncio do Facebook gera uma entrada de cache única para o que é, da perspectiva da aplicação, a mesma página. Durante um período de campanha, isso fragmenta o cache do Varnish em milhares de entradas frias e elimina completamente o benefício da taxa de acertos.
A configuração grace em vcl_backend_response é o outro mecanismo que vale entender em detalhes. Quando o TTL de um objeto em cache expira, a próxima requisição normalmente esperaria o Nginx retornar uma resposta fresca antes do Varnish entregar qualquer coisa. Sob um pico de tráfego — um envio de newsletter, uma menção em redes sociais — múltiplas requisições chegam simultaneamente para a entrada expirada. Sem grace, cada requisição passa para o Nginx concorrentemente. Com um período de grace de 60 segundos, o Varnish serve o objeto obsoleto para todas as requisições, exceto uma, enquanto um único fetch em segundo plano revalida o cache. O resultado é que o Nginx lida com uma requisição em vez de centenas no momento da expiração.
Implicações de Desempenho
A história de desempenho desta stack não é uniforme. Há lugares onde ela se destaca e lugares onde adiciona overhead mensurável.
Onde tem bom desempenho: qualquer página cacheável servida da memória do Varnish retorna em menos de 2ms no nível do Traefik. Uma página inicial do WordPress que leva 400ms para ser gerada pelo PHP é servida em 1–2ms para cada requisição subsequente até o TTL expirar. A melhoria de throughput não é incremental — é uma ordem de grandeza. O serving de ativos estáticos através do Nginx é rápido independentemente, e o Cloudflare armazena esses ativos em cache na borda de qualquer forma, então o Nginx raramente os lida em produção.
Onde adiciona overhead: cache misses agora atravessam quatro hops em vez de um. Uma requisição PHP que não pode ser cacheada — uma sessão de usuário logado, um envio POST, uma página de carrinho do WooCommerce — passa pelo Traefik, Varnish e Nginx antes de chegar ao PHP-FPM. Cada hop de rede Docker no mesmo host adiciona aproximadamente 0,1–0,3ms de latência. Para uma resposta PHP de 50ms, isso é aceitável. Para um endpoint de API que deve responder em menos de 5ms, não é. Nesse caso, a abordagem correta é rotear o caminho da API diretamente para o contêiner da aplicação via uma regra de roteador Traefik separada, contornando o Varnish completamente.
O dimensionamento de memória importa mais do que a maioria dos parâmetros: O Varnish aloca seu armazenamento de objetos da RAM. A configuração padrão usa 256MB. Para um site com muitas URLs únicas, isso se preenche rapidamente, a taxa de despejo aumenta e a taxa de acertos do cache permanece baixa independentemente das configurações de TTL. A primeira alavanca de desempenho a puxar após o deployment é aumentar a alocação — o flag de inicialização -s malloc,2G, ou a variável de ambiente VARNISH_SIZE=2G no Docker — e monitorar o resultado com varnishstat -1 | grep hit.
Encerramento de SSL: O TLS encerra no Cloudflare. Em uma configuração fronteada pelo Cloudflare, os segmentos Traefik-para-Varnish e Varnish-para-Nginx rodam sobre HTTP simples na rede bridge interna do Docker. Isso é intencional. Adicionar TLS à comunicação interna entre contêineres no mesmo host introduz overhead de CPU sem uma melhoria significativa de segurança — o modelo de ameaça para tráfego de bridge Docker é categoricamente diferente do tráfego da internet pública. Se seu modelo de ameaça requer comunicação criptografada entre contêineres, essa é uma conversa arquitetural diferente.
Quando Esta Stack Faz Sentido
Esta não é uma recomendação de uso geral. Para uma única aplicação containerizada sem requisitos de cache e sem a restrição do CloudPanel, o Traefik roteando diretamente para o app é mais simples e correto. A stack de quatro camadas justifica sua complexidade em cenários específicos: ambientes gerenciados pelo CloudPanel onde o Nginx não é negociável, sites baseados em CMS onde o cache de página completa proporciona melhorias de throughput de ordem de grandeza, ou deployments multi-serviço onde o Traefik já é a camada de roteamento para uma dúzia de contêineres e adicionar Varnish para um deles é custo incremental, não um novo sistema.
A separação de responsabilidades é o que torna a complexidade gerenciável ao longo do tempo. A configuração do Traefik vive em labels do Docker Compose. A lógica do Varnish vive em arquivos VCL com controle de versão junto com a stack compose. A configuração do Nginx é de propriedade do CloudPanel. Nenhum desses sistemas alcança o domínio dos outros. Quando algo se comporta de forma inesperada, o caminho de diagnóstico é claro: o header X-Cache diz se o Varnish serviu a resposta; o log de acesso do Traefik registra qual middleware rodou e o que o upstream recebeu; o log de erros do Nginx registra o que o PHP-FPM reportou. Cada camada produz seu próprio sinal observável, e as falhas não se acumulam silenciosamente entre as camadas.
O que parece redundância é, na prática, um limite claro entre roteamento, cache, serving de aplicação e computação — quatro preocupações que escalam independentemente e falham independentemente. Esse limite vale a superfície adicional de configuração.
Insights Relacionados
- Implantando Aplicações React em Produção: Configuração Completa com Docker e Traefik como Proxy Reverso
- Guia Completo de Solução de Problemas do n8n Self-Hosted 2025: Corrigindo Problemas de Tamanho de Dados de Execução & Webhooks com Traefik
- Construindo uma Stack de Desenvolvimento Multi-Tenant com Docker: Configuração Completa para Deployments de Clientes Escaláveis
- Otimização de Desempenho do CloudPanel: Maximizando o Desempenho do Servidor Hetzner Cloud para Entrega Ultrarrápida de Sites