Um health check mal configurado consegue derrubar um SaaS que tem três servidores, dois balanceadores e redundância no papel. O load balancer só sabe o que você manda ele verificar. Se a verificação mente, ele manda tráfego para um servidor quebrado ou tira do ar todos os servidores saudáveis de uma vez. Em geral a causa é uma destas cinco: checar só a porta, checar dependências demais, confundir readiness com liveness, calibrar mal os intervalos e ignorar o desligamento das instâncias.

Este texto vale para quem roda o próprio balanceador (HAProxy, Nginx, Traefik, Envoy), para quem usa o load balancer gerenciado de um provedor de cloud e para quem está em Kubernetes. A ferramenta muda. A lógica é a mesma em todos os casos, e os erros também.

O que é um health check no balanceamento de carga

Health check é a verificação periódica que o load balancer faz em cada servidor de backend para decidir se ele pode receber tráfego. Se a verificação falha um número definido de vezes seguidas, o servidor sai do pool. Quando volta a passar, ele retorna. É o mecanismo que transforma servidores redundantes em alta disponibilidade de verdade.

Sem health check, o balanceamento de carga só distribui requisições, seja em round-robin, por menor número de conexões ou por hash de IP. Ele distribui também para o servidor que travou há dez minutos. Com o health check, o balanceador ganha a capacidade de reagir sozinho a uma falha, sem ninguém acordar às três da manhã para tirar um nó do ar na mão.

Existem duas famílias de verificação, e muita gente só conhece a primeira:

  • Health check ativo: o balanceador envia sondas por conta própria, como uma conexão TCP ou um GET /healthz, a cada X segundos, e avalia a resposta.
  • Health check passivo (ou detecção de outliers): o balanceador observa o tráfego real. Se um backend começa a devolver muitos erros 5xx ou a estourar timeout, ele é ejetado temporariamente.

Os dois se complementam. O ativo detecta o servidor morto antes de um usuário real cair nele. O passivo pega o servidor que responde ao /healthz com um 200 bonito enquanto falha em metade das requisições de verdade. Num SaaS com SLA, vale usar ambos sempre que a ferramenta permitir.

Erro 1: checar só se a porta está aberta

O erro mais comum é também o mais silencioso. O health check TCP abre uma conexão na porta 443 ou 8080, recebe o handshake e marca o servidor como saudável. O problema é que porta aberta não quer dizer aplicação funcionando.

Já vi cada um destes cenários passar ileso por uma checagem TCP:

  • O pool de workers do PHP-FPM ou do Gunicorn está todo ocupado. O socket aceita a conexão, mas a requisição fica na fila até dar timeout.
  • Um Nginx na frente da aplicação continua de pé enquanto o processo da aplicação atrás dele morreu. A porta 80 responde e devolve 502 para todo mundo.
  • A aplicação entrou em deadlock depois de um vazamento de conexões com o banco. O processo está vivo e a porta está aberta, mas nada é processado.
  • O disco encheu e a aplicação passou a falhar em toda escrita de sessão ou de upload.

Em todos esses casos o load balancer segue enviando uma fração do tráfego ao nó quebrado. Com três servidores, um terço dos usuários vê erro. O monitoramento de uptime externo às vezes nem dispara, porque acerta o servidor bom duas vezes em cada três.

Como corrigir

Use um health check HTTP apontando para um endpoint dedicado, servido pela própria aplicação e não pelo proxy reverso local. Esse endpoint precisa passar pelo mesmo caminho de uma requisição comum: entrar pelo servidor web, chegar a um worker da aplicação, executar código e devolver resposta. Se o pool de workers estiver esgotado, a checagem vai estourar o timeout, que é o comportamento certo.

Um exemplo em HAProxy:

backend app
    option httpchk GET /healthz
    http-check expect status 200
    server app1 10.0.0.11:8080 check inter 2s fall 3 rise 2
    server app2 10.0.0.12:8080 check inter 2s fall 3 rise 2

Um detalhe derruba muita gente: o endpoint que devolve um 200 OK estático, escrito como arquivo no Nginx, tem o mesmo defeito do check TCP. Ele prova que o Nginx está vivo e nada além disso. O endpoint precisa ser atendido pela aplicação.

Regra prática: se você consegue derrubar a aplicação e o health check continua passando, ele não serve para nada. Teste isso de propósito em homologação.

Erro 2: checar dependências demais e derrubar tudo junto

Quem descobre o erro 1 costuma exagerar na correção. O /healthz passa a testar o PostgreSQL, o Redis, o bucket de objetos, a fila de mensagens e às vezes a API do gateway de pagamento. A lógica parece boa: se alguma coisa está quebrada, o servidor não está saudável. Na prática, essa lógica causa indisponibilidade total.

Pense no que acontece quando o banco de dados fica lento por trinta segundos durante um vacuum pesado ou um pico de escrita. Todas as instâncias da aplicação dependem do mesmo banco. Todos os health checks falham ao mesmo tempo, e o load balancer remove todos os backends do pool. Uma degradação parcial, em que algumas telas ficariam lentas mas o login e o painel funcionariam, vira um 503 para 100% dos usuários.

Isso é falha em cascata provocada pelo próprio mecanismo de alta disponibilidade. E não resolve nada, porque tirar servidores de aplicação do pool não acelera o banco.

O critério que funciona

A pergunta que o health check do balanceador deve responder é: "se eu tirar este servidor do pool e mandar o tráfego para os outros, o usuário fica melhor?" Quando a resposta for "não, porque os outros têm o mesmo problema", essa dependência não entra na checagem.

Aplicando o critério:

  • Entram: estado local da instância. O processo responde, a configuração foi carregada, o disco local tem espaço, o cache em memória foi aquecido, a conexão local com um sidecar está de pé.
  • Ficam de fora: dependências compartilhadas por todas as instâncias, como banco principal, Redis central, storage de objetos e APIs de terceiros.
  • Caso intermediário: se cada instância tem o próprio pool de conexões com o banco, faz sentido verificar se esse pool não está esgotado ou corrompido. Esse é um problema local. A falha do banco em si continua de fora.

As dependências compartilhadas continuam importantes. Elas vão para um endpoint separado, algo como /health/deep ou /status, consultado pelo monitoramento (Zabbix, Prometheus, Uptime Kuma, o que você usar). Esse endpoint dispara alerta para um humano e não tira nada do pool sozinho.

Alguns balanceadores adotam o comportamento chamado fail open: se todos os backends ficam unhealthy, eles voltam a mandar tráfego para todos, já que não há alternativa melhor. Outros simplesmente devolvem 503. Confira na documentação do seu como ele se comporta, mas não conte com isso como rede de proteção. Projete o health check para que esse cenário nunca aconteça por causa de uma dependência compartilhada.

Um último cuidado: se o check profundo faz consultas ao banco, ele gera carga. Com três nós de balanceador checando dez servidores a cada dois segundos, são 15 consultas por segundo só de health check. Num banco já sofrendo, isso pesa.

Erro 3: tratar readiness e liveness como a mesma coisa

Quem trabalha com Kubernetes já viu os termos readiness e liveness. Quem não trabalha também deveria conhecer, porque o conceito vale para qualquer arquitetura com balanceador e algum tipo de supervisor de processos (systemd, Docker com restart policy, um watchdog).

São duas perguntas diferentes, com consequências diferentes:

VerificaçãoPergunta que respondeO que acontece quando falhaErro típico
Readiness"Posso receber tráfego agora?"A instância sai do balanceamento de carga, mas continua rodandoIncluir dependências compartilhadas (ver erro 2)
Liveness"Estou travado sem chance de me recuperar?"A instância é reiniciadaChecar banco ou serviços externos e causar uma tempestade de restarts
Startup"Já terminei de inicializar?"Readiness e liveness ficam suspensos até passarNão usar, e ver aplicação lenta para subir ser morta em loop

O estrago aparece quando alguém aponta a liveness para o mesmo endpoint profundo da readiness. O banco oscila, a liveness falha em todos os pods, o Kubernetes reinicia todos e os pods sobem disputando conexões com um banco que já estava sobrecarregado. Isso piora a situação e entra em loop. Uma lentidão de um minuto vira uma indisponibilidade de vinte.

Como separar na prática

  1. Liveness deve ser quase burra. Ela verifica se o processo consegue executar código e responder. Um endpoint que só confirma que a thread principal não está travada já basta. Ela só deve falhar quando o reinício for a única saída.
  2. Readiness verifica a capacidade local de atender. Cache aquecido, migrações locais concluídas, pool de conexões funcional, instância fora do modo de desligamento.
  3. Startup probe protege a inicialização lenta. Aplicações JVM, Rails com muitos gems ou serviços que carregam modelos grandes em memória podem levar de 30 a 90 segundos para subir. Sem startup probe, a liveness mata o processo antes de ele terminar de ligar, e você vê o pod em CrashLoopBackOff sem nenhum bug no código.

Fora do Kubernetes, o raciocínio se traduz assim: o health check do HAProxy ou do Nginx faz o papel da readiness. O systemd com WatchdogSec, ou o script que reinicia o serviço, faz o papel da liveness. Se os dois olham para o mesmo endpoint profundo, o problema é idêntico.

Também tem a situação em que você nem deveria ter liveness. Se a aplicação não tem histórico de travar sem morrer, e o processo encerra sozinho quando quebra, o supervisor já reinicia por conta própria. Uma liveness mal desenhada traz mais risco do que nenhuma.

Erro 4: intervalos, timeouts e thresholds no chute

Mesmo com o endpoint certo, os números do health check decidem se o failover acontece em segundos ou em minutos, e se ele dispara à toa. Quatro parâmetros importam:

  • Intervalo: de quanto em quanto tempo o balanceador checa.
  • Timeout: quanto ele espera pela resposta antes de considerar falha.
  • Unhealthy threshold (fall): quantas falhas seguidas tiram o servidor do pool.
  • Healthy threshold (rise): quantos sucessos seguidos o trazem de volta.

O tempo para detectar uma falha fica perto de intervalo × fall + timeout. Com os padrões conservadores de alguns provedores, como intervalo de 30 segundos e três falhas, um servidor morto pode receber tráfego por mais de um minuto e meio. Num SaaS com milhares de requisições por minuto, isso significa muitos erros entregues a clientes reais antes de qualquer reação.

O exagero oposto também quebra. Intervalo de 1 segundo com fall de 1 faz qualquer pausa de garbage collection, pico de CPU durante um backup ou perda de pacote isolada tirar o servidor do pool. Ele volta no segundo seguinte e sai de novo logo depois. Esse flapping gera conexões cortadas, logs inúteis e, em alguns balanceadores, reequilíbrio constante de sessões.

Perfis de referência

Não existe número universal, mas estes perfis servem de ponto de partida para a maioria dos SaaS:

PerfilIntervaloTimeoutFallRiseDetecção aproximadaQuando usar
Agressivo2s1s23~5sAPI com SLA apertado e latência baixa e estável
Equilibrado5s2s32~15–17sMaioria das aplicações web e SaaS B2B
Conservador10s5s33~35sAplicações com picos de latência conhecidos (relatórios, GC longo)

Algumas pegadinhas que só aparecem depois que dá problema:

  1. Timeout maior ou igual ao intervalo. As checagens se sobrepõem, e o comportamento varia de ferramenta para ferramenta. Mantenha o timeout bem abaixo do intervalo.
  2. Timeout do health check maior que o timeout da requisição real. Se o usuário desiste em 5 segundos e o check aceita 10, o servidor que responde em 8 segundos fica "saudável" e entrega erro para todo mundo.
  3. Rise igual a 1. Um servidor instável volta ao pool na primeira resposta boa. Prefira voltar devagar e sair rápido. Rise igual ou maior que fall costuma ser a escolha mais segura.
  4. Esquecer a multiplicação. Cada nó de balanceador checa cada backend. Em setups com vários balanceadores, ou com muitos serviços passando pelo mesmo endpoint, o tráfego de health check vira uma fração visível dos seus logs. Filtre esse endpoint do log de acesso, ou você vai pagar storage para guardar milhões de linhas de GET /healthz 200.

Calibre com dados. Olhe o p99 de latência do endpoint de health check ao longo de uma semana, inclusive nos horários de backup e de jobs pesados. O timeout tem que ficar com folga acima desse p99. Se não ficar, o problema está no endpoint, não no balanceador.

Erro 5: ignorar o desligamento das instâncias e nunca testar o failover

Muito SaaS tem health check bem feito e mesmo assim exibe uma rajada de erros 502 a cada deploy. A causa quase nunca está no código novo. Está na ordem dos eventos quando a instância antiga é desligada.

Imagine uma plataforma de agendamento online que faz deploy às duas da tarde, em horário comercial, com rolling update. O orquestrador manda SIGTERM para a instância antiga, e a aplicação encerra na hora. O balanceador, que checa a cada 5 segundos e precisa de 3 falhas, só percebe a saída depois de cerca de 15 segundos. Durante esse tempo ele continua mandando requisições para um servidor que não existe mais. Cada deploy produz alguns segundos de erro. Com dez deploys por semana, o cliente começa a reclamar de "instabilidade aleatória" que ninguém consegue reproduzir.

A sequência correta de desligamento

  1. A instância recebe o sinal de término (SIGTERM).
  2. Ela passa a falhar o readiness de propósito e continua atendendo as requisições que chegam.
  3. Ela espera um período maior que intervalo × fall do balanceador, tempo para ele perceber e parar de mandar tráfego novo.
  4. Ela termina as requisições em andamento (connection draining).
  5. Só então encerra o processo.

No Kubernetes, isso costuma ser resolvido com um preStop que faz um sleep de alguns segundos, somado a um terminationGracePeriodSeconds compatível com a requisição mais longa da aplicação. No HAProxy dá para colocar o servidor em estado drain pela runtime API antes do deploy. Em load balancers gerenciados, procure a configuração de deregistration delay ou connection draining, que frequentemente vem com um valor padrão que ninguém revisou.

Atenção a WebSockets e conexões longas. Se o seu SaaS tem chat, notificações em tempo real ou upload grande, o draining precisa de mais tempo, ou o cliente precisa saber reconectar sem perder estado. Muitas vezes a correção certa fica no frontend, com reconexão automática e backoff.

O failover que ninguém testou

A segunda metade deste erro é confiar num failover que nunca foi exercitado. Três falhas aparecem com frequência:

  • O balanceador é ponto único de falha. Dois servidores de aplicação atrás de um único HAProxy numa única VM não formam alta disponibilidade, só mudam o ponto de falha de lugar. Para balanceador próprio, use pelo menos dois nós com IP flutuante via keepalived/VRRP, ou um balanceador gerenciado que já seja redundante.
  • A capacidade não cobre a perda de um nó. Três servidores rodando a 80% de CPU não sobrevivem à queda de um: os dois restantes iriam para 120%. O failover funciona e a degradação continua. A conta é N+1: o sistema precisa aguentar o pico com um nó a menos.
  • Sessão presa no servidor. Se a sessão do usuário fica em memória local e o balanceador usa sticky session, a queda de um nó desloga todo mundo que estava nele. Sessão em Redis ou em token assinado evita esse problema.

A única forma de saber se o failover funciona é derrubar um nó de propósito, em horário controlado, e observar. Desligue uma instância, mate o processo da aplicação, encha o disco de um servidor de teste, bloqueie a porta no firewall. Meça quanto tempo o balanceador leva para reagir e quantos erros os usuários sintéticos recebem. Faça isso pelo menos a cada mudança relevante de infraestrutura. Um teste por trimestre já põe você à frente da maioria.

Failover que nunca foi testado é uma hipótese. Você vai descobrir se ela é verdadeira no pior momento possível.

Checklist rápido para revisar seus health checks hoje

  • O check é HTTP e atendido pela aplicação, não pelo proxy local nem só por TCP.
  • O endpoint do balanceador não depende de banco, cache ou APIs compartilhados.
  • Existe um endpoint profundo separado, ligado ao monitoramento e aos alertas.
  • Liveness e readiness estão separados, se houver orquestrador ou watchdog.
  • O timeout fica abaixo do intervalo e acima do p99 do endpoint.
  • Rise é maior ou igual a fall.
  • O desligamento falha o readiness, espera e drena antes de encerrar.
  • O balanceador tem redundância.
  • A capacidade segura o pico com um nó a menos.
  • O failover foi testado nos últimos meses.

Perguntas frequentes

Qual a diferença entre health check ativo e passivo?

O ativo envia requisições de teste ao backend em intervalos fixos e decide com base na resposta. O passivo analisa o tráfego real e ejeta o servidor que acumula erros ou timeouts. O ativo pega servidores mortos antes de qualquer usuário. O passivo pega falhas parciais que o endpoint de teste não revela. Quando possível, use os dois.

Devo usar /health, /healthz ou /status?

O nome importa pouco. O que importa é a separação de responsabilidades. Um padrão comum é /healthz ou /livez para liveness, /readyz para readiness e um endpoint profundo, como /status, só para monitoramento. Escolha uma convenção, documente e mantenha igual em todos os serviços.

O endpoint de health check precisa de autenticação?

O endpoint do balanceador normalmente fica sem autenticação, porque o balanceador precisa acessá-lo sem credenciais. Ele não deve expor versões de bibliotecas, hostnames internos ou mensagens de erro detalhadas. Se o endpoint profundo revelar o estado das dependências, restrinja o acesso por rede (só a rede interna ou o IP do monitoramento) ou por token.

Com que frequência o load balancer deve checar os servidores?

Para a maioria dos SaaS, um intervalo entre 2 e 10 segundos, com 2 a 3 falhas para remover o servidor, equilibra detecção rápida e estabilidade. Abaixo disso aumenta o risco de flapping. Acima, o servidor quebrado recebe tráfego por tempo demais. Calibre a partir da latência real do endpoint, medida em horários de pico.

Health check substitui monitoramento?

Não. O health check toma uma decisão automática e local: este servidor recebe tráfego ou não. O monitoramento observa tendências, dependências compartilhadas, latência, taxa de erro e capacidade, e avisa um humano. Muitos problemas sérios, como banco degradando aos poucos ou disco enchendo em ritmo constante, nunca devem acionar o health check, mas precisam acionar alerta.

Ter load balancer garante alta disponibilidade no SaaS?

Sozinho, não. A alta disponibilidade depende de balanceador redundante, health checks bem desenhados, capacidade N+1, sessão fora da memória local, banco com réplica e failover, deploys com draining e testes periódicos de falha. O load balancer é uma peça central, mas qualquer componente único na arquitetura continua sendo ponto de falha.

Conclusão

Um health check bem feito parece pouca coisa: um endpoint, quatro números e uma rotina de desligamento. Ainda assim, ele separa um SaaS que absorve falhas em silêncio de outro que transforma cada oscilação de banco ou cada deploy em incidente. Os cinco erros têm a mesma origem. A verificação não reflete o que o usuário vive: ela mente para mais (porta aberta com aplicação travada) ou para menos (dependência compartilhada derrubando todas as instâncias juntas).

Se você só tiver tempo para uma ação esta semana, passe pelo checklist acima e derrube um servidor de propósito em horário controlado. O que você observar nesses minutos vai mostrar a saúde real do seu balanceamento de carga melhor do que qualquer diagrama de arquitetura.

A Toda Solução trabalha com isso todos os dias. Desenhamos e operamos ambientes de cloud, VPS e clusters Proxmox com balanceamento de carga redundante, health checks calibrados para a sua aplicação, monitoramento separado das decisões automáticas e testes de failover documentados. Se o seu SaaS cresceu mais rápido que a infraestrutura, ou se cada deploy ainda gera aquela onda de 502, fale com o nosso time para revisar a sua arquitetura.