Buzeli
buzeliSoluções Digitais
Incidentes

Site retornando 500 com WAF saudável: quando o nginx backend some e o proxy reverso fica cego

Publicado em 2 de maio de 2026

O sintoma: 500 com infraestrutura aparentemente saudável

Por volta das 08:09 UTC o servidor reiniciou. Às 13:35 UTC, quase cinco horas e meia depois, o problema ainda estava ativo: todos os visitantes de um portal de conteúdo de alto tráfego recebiam HTTP 500. Nenhum alerta havia disparado sobre o WAF ou sobre o container nginx — ambos estavam de pé.

A arquitetura do servidor tem duas camadas nginx distintas dentro do mesmo host:

Copiar
# Fluxo de requisição
CDN (Akamai) → WAF container (network_mode: host, porta 443)
             → proxy_pass https://172.28.5.61:443
             → nginx backend (rede bridge, IP 172.28.5.61)
             → fastcgi → PHP-FPM

O WAF roda em network_mode host — ele usa diretamente a interface de rede da VM, enxerga IPs reais dos edge nodes da Akamai e faz proxy para o nginx backend que está em uma rede bridge interna (172.28.5.61). O PHP-FPM também corre em network_mode host para compartilhar o socket Unix com o nginx backend.

Quando a camada externa está saudável e a camada interna falha silenciosamente, o sistema de monitoramento tradicional não detecta. O healthcheck do WAF responde 200. A camada interna que sumiu é invisível para quem monitora de fora.

O diagnóstico: vhost com 0 bytes

A primeira checagem foi no error.log do WAF. A mensagem era direta:

Copiar
connect() failed (111: Connection refused) while connecting to upstream https://172.28.5.61:443/

O WAF estava tentando fazer proxy para o nginx backend em 172.28.5.61:443 e recebendo connection refused. O container nginx estava rodando — o processo existia. Mas o nginx não estava escutando na porta 443 para aquele domínio específico.

A causa ficou clara ao verificar o arquivo de vhost:

Copiar
# Verificar tamanho do arquivo de vhost no host
ls -la /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br

# Saída:
-rw-r--r-- 1 root root 0 Apr 13 08:09 blog.cliente-exemplo.com.br

Zero bytes. O arquivo de configuração do vhost estava completamente vazio. Quando o nginx recarrega com um arquivo de configuração de vhost vazio, ele simplesmente não cria o server block correspondente — sem listen 443, sem proxy_pass, sem nada. O nginx continua funcionando normalmente para todos os outros domínios configurados. Mas para esse domínio específico, a porta 443 não existe.

O WAF, ao receber a requisição da Akamai para aquele domínio, tentava o proxy_pass configurado e encontrava a porta fechada. A Akamai recebia connection refused e surfaceava isso como ERR_READ_ERROR nos browsers dos visitantes.

Por que o vhost ficou vazio

O servidor havia reiniciado às 08:09 UTC. O arquivo de backup do vhost existia no diretório home do servidor — criado em uma sessão de manutenção anterior:

Copiar
# Backups disponíveis no host
/home/developer/webserver/blog.cliente-exemplo.com.br.bak.20260319       # nginx vhost
/home/developer/webserver/blog.cliente-exemplo.com.br.waf-bak.20260319   # WAF vhost

A hipótese mais provável é que uma operação de manutenção durante ou antes do reinício sobrescreveu o arquivo de vhost com conteúdo vazio — seja um truncamento acidental, seja um deploy que falhou no meio. O arquivo de WAF correspondente (waf-enabled/blog.cliente-exemplo.com.br) estava íntegro — o que explica por que o WAF em si estava saudável e aceitando conexões na porta 443, mas falha ao tentar repassar para o backend.

O WAF saudável mascara o backend quebrado. Do ponto de vista do CDN, a origin está respondendo — só que com 502 ou connection refused em vez de 200. Para o monitoramento externo, a origin é o WAF, não o nginx backend.

A resolução: restore do backup

A correção foi direta — restaurar o vhost a partir do backup e recarregar o nginx backend:

Copiar
# Restaurar vhost nginx a partir do backup
cp /home/developer/webserver/blog.cliente-exemplo.com.br.bak.20260319    /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br

# Verificar que o arquivo não está mais vazio
wc -c /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br
# Saída esperada: algo como "4321 /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br"

# Testar configuração nginx antes do reload
docker exec nginx nginx -t

# Recarregar nginx (sem downtime)
docker exec nginx nginx -s reload

Às 13:35 UTC, após o reload, o site voltou a responder 200. O WAF começou a receber respostas válidas do backend. Os visitantes pararam de ver o erro.

A lição de arquitetura: monitorar cada camada pela perspectiva da camada anterior

Esse incidente expõe um ponto cego clássico em arquiteturas de múltiplas camadas: o healthcheck monitora a camada mais externa, mas não valida que as camadas internas estão funcionando corretamente.

No diagrama abaixo, cada seta representa uma dependência que pode falhar silenciosamente:

Copiar
# Cadeia de dependências — cada camada pode falhar sem que a anterior saiba
[Monitoramento externo] → verifica se WAF responde na porta 443
[WAF]                   → verifica se nginx backend responde em 172.28.5.61:443
[nginx backend]         → verifica se PHP-FPM processa fastcgi
[PHP-FPM]               → verifica se banco de dados responde

# O que estava sendo monitorado:
[Monitoramento externo] → WAF porta 443: OK (WAF responde, mas retorna 500 para o domínio)

# O que deveria estar sendo monitorado:
[WAF]                   → nginx backend: FALHA (connection refused em 172.28.5.61:443)

O ponto crítico é que o monitoramento precisa validar a saúde do backend a partir da perspectiva do WAF — não apenas a saúde do WAF a partir da perspectiva externa. Um healthcheck que faz curl no IP do WAF pode retornar 200 enquanto todos os usuários recebem 500, se o WAF estiver retornando sua própria página de erro.

Como detectar este padrão antes do incidente

Duas verificações simples que detectam o problema antes que os visitantes o reportem:

Copiar
# 1. Verificar se todos os vhosts têm tamanho não-zero
find /home/developer/webserver/sites-enabled/ -maxdepth 1 -type f -empty
# Se retornar algum arquivo: ALERTA — vhost vazio

# 2. Verificar se o nginx backend está escutando nas portas esperadas
docker exec nginx ss -tlnp | grep :443
# Se retornar vazio para um domínio que deveria estar ativo: PROBLEMA

# 3. Testar o proxy interno diretamente (bypassa o WAF)
curl -sk -H "Host: blog.cliente-exemplo.com.br" https://172.28.5.61/ -o /dev/null -w "%{http_code}
"
# Se retornar 000 ou connection refused: nginx backend não está escutando

A verificação via curl direto no IP interno do nginx backend é particularmente útil porque simula exatamente o que o WAF faz ao receber uma requisição — e falha da mesma forma, tornando o problema imediatamente visível.

O que foi corrigido após o incidente

Backup preservado fora de sites-enabled: arquivos .bak nunca devem ficar dentro de sites-enabled ou waf-enabled — o nginx carrega todos os arquivos desses diretórios e gera conflicting server_name warnings.

Monitoramento do backend interno: adicionado healthcheck que verifica curl direto em 172.28.5.61 com Host header — detecta ausência de listen 443 antes que o WAF comece a retornar connection refused para os visitantes.

Verificação de vhosts vazios no deploy: qualquer pipeline que escreva arquivos em sites-enabled deve verificar tamanho não-zero antes de recarregar o nginx.

Infraestrutura em camadas aumenta a resiliência — mas também aumenta a distância entre o ponto de falha e o ponto de detecção. Cada camada precisa de visibilidade sobre a saúde da camada que ela depende, não apenas sobre a sua própria.