Health checks são endpoints ou sinais usados para indicar se uma aplicação Node.js está viva, pronta para receber tráfego e funcionando de acordo com critérios operacionais. Eles orientam balanceadores, orquestradores, supervisores e equipes de suporte.
Um health check mal projetado pode causar mais indisponibilidade que o problema original. Se a verificação depende de todos os serviços externos, uma falha parcial pode retirar todas as réplicas do ar. O desenho deve separar liveness, readiness e startup.
Liveness
Liveness responde: “o processo está vivo e capaz de continuar?”. Ela deve ser simples e local. Quando falha de forma sustentada, a plataforma pode reiniciar o container.
app.get('/health/live', (req, res) => {
res.json({ status: 'alive' });
});Não consulte banco ou API externa na liveness. Uma dependência indisponível não significa necessariamente que reiniciar o processo resolverá.
Readiness
Readiness responde: “esta instância pode receber tráfego agora?”. Ela pode considerar inicialização, shutdown, pools essenciais e estado interno.
let ready = false;
let shuttingDown = false;
app.get('/health/ready', (req, res) => {
if (!ready || shuttingDown) {
res.status(503).json({ status: 'not-ready' });
return;
}
res.json({ status: 'ready' });
});Ao iniciar, defina ready=true apenas depois de concluir etapas críticas. No shutdown, defina shuttingDown=true antes de fechar o servidor.
Startup probe
Aplicações com bootstrap demorado podem usar uma verificação de startup. Enquanto ela não passa, a plataforma evita aplicar liveness agressiva. Isso impede reinícios em loop durante migrations, aquecimento ou carregamento de modelos.
Health check detalhado
app.get('/health', async (req, res) => {
const checks = await runChecks();
const healthy = checks.every((check) => check.status === 'up');
res.status(healthy ? 200 : 503).json({
status: healthy ? 'up' : 'down',
checks,
});
});Esse endpoint pode ser útil para operadores, mas não deve necessariamente controlar o balanceamento. Proteja detalhes internos e aplique timeout.
Dependências essenciais e opcionais
Classifique dependências:
- essencial: sem ela nenhuma rota principal funciona;
- degradável: a aplicação consegue oferecer resposta parcial;
- opcional: recurso secundário pode ficar indisponível;
- assíncrona: fila ou sistema que pode acumular temporariamente.
Readiness não deve falhar por uma integração opcional se o serviço ainda entrega seu objetivo principal.
Check de banco de dados
async function checkDatabase({ signal }) {
const start = performance.now();
try {
await database.query('SELECT 1', { signal });
return {
name: 'database',
status: 'up',
durationMs: performance.now() - start,
};
} catch (error) {
return {
name: 'database',
status: 'down',
durationMs: performance.now() - start,
error: error.name,
};
}
}Use uma consulta barata e limite tempo. Não registre credenciais, SQL sensível ou mensagem completa no corpo público.
Timeout global
async function runChecks() {
const signal = AbortSignal.timeout(1000);
return Promise.all([
checkDatabase({ signal }),
checkRedis({ signal }),
]);
}Sem timeout, a própria verificação pode ficar pendurada, fazendo o balanceador interpretar como falha após o prazo externo.
Não sobrecarregue dependências
Se dezenas de réplicas recebem probes a cada segundo, o banco pode receber milhares de checks. Estratégias:
- cache curto do resultado;
- intervalo adequado;
- consulta mínima;
- jitter entre instâncias;
- limite de concorrência;
- check passivo baseado em uso real.
Cacheando resultado
let cached = null;
let expiresAt = 0;
async function cachedReadiness() {
if (Date.now() < expiresAt && cached) return cached;
cached = await calculateReadiness();
expiresAt = Date.now() + 2000;
return cached;
}Use proteção contra várias atualizações simultâneas. O TTL cria uma janela de desatualização, por isso deve ser pequeno e intencional.
Check passivo
Em vez de consultar uma dependência apenas para health check, registre o resultado das operações reais. Se o pool está conectado e consultas recentes tiveram sucesso, readiness pode usar esse estado.
Combine sinal passivo com uma verificação ativa periódica para detectar ausência de tráfego.
Event loop
Um processo pode responder ao endpoint e ainda estar saturado. Acompanhe event loop utilization e delay em métricas. Evite tornar readiness extremamente sensível a um único pico, pois retirar instâncias aumenta a carga nas restantes.
Memória
Readiness pode considerar pressão de memória antes de OOM, mas use hysteresis:
const HIGH_RSS = 1_500_000_000;
const LOW_RSS = 1_300_000_000;
let memoryReady = true;
function updateMemoryState() {
const rss = process.memoryUsage().rss;
if (memoryReady && rss > HIGH_RSS) memoryReady = false;
if (!memoryReady && rss < LOW_RSS) memoryReady = true;
}Sem faixas distintas, o estado pode oscilar. Investigue a causa do crescimento.
Fila interna
Se a aplicação usa fila limitada, readiness pode falhar quando não há capacidade para novas requisições. Novamente, aplique hysteresis e confirme que remover a instância permitirá drenagem.
Degraded
Um endpoint operacional pode retornar degraded quando recursos opcionais falham:
{
"status": "degraded",
"checks": [
{ "name": "database", "status": "up" },
{ "name": "recommendations", "status": "down" }
]
}Para o balanceador, a instância pode continuar pronta. Para dashboards, o estado degradado gera alerta.
Status HTTP
- 200: verificação passou;
- 503: instância não está disponível;
- 500: normalmente evite, pois falha é estado esperado do check;
- 401/403: pode quebrar probes se autenticação não estiver configurada.
Endpoints do orquestrador podem ficar em porta ou rede interna sem autenticação, desde que não sejam expostos publicamente.
Informações no corpo
Exponha o mínimo:
{
"status": "ready"
}Versão, commit, nomes de dependências e erros detalhados devem ficar em endpoint autenticado ou logs. Isso reduz reconhecimento da infraestrutura por atacantes.
Build info
Um endpoint interno pode fornecer:
app.get('/internal/info', requireInternal, (req, res) => {
res.json({
version: process.env.APP_VERSION,
commit: process.env.GIT_SHA,
startedAt,
});
});Não misture info com readiness se o balanceador só precisa do status.
Kubernetes
livenessProbe:
httpGet:
path: /health/live
port: 3000
periodSeconds: 10
timeoutSeconds: 2
readinessProbe:
httpGet:
path: /health/ready
port: 3000
periodSeconds: 5
timeoutSeconds: 2Ajuste thresholds e startupProbe ao tempo real. Probes agressivas podem reiniciar aplicações durante pausas temporárias.
Docker Compose
healthcheck:
test: ["CMD", "node", "scripts/healthcheck.js"]
interval: 10s
timeout: 3s
retries: 3Não dependa de curl em imagens que não o incluem. Um script Node.js pode fazer a chamada.
Load balancer
Configure caminho, timeout, intervalo, códigos esperados e número de falhas. Uma divergência entre plataforma e aplicação pode manter instância ruim ou remover instância saudável.
Graceful shutdown
O fluxo correto:
- receber SIGTERM;
- marcar readiness como 503;
- aguardar retirada do balanceador;
- parar de aceitar conexões;
- drenar requisições;
- fechar recursos;
- encerrar.
Health check e autoscaling
Health check não é métrica de scaling. Use CPU, fila, latência ou métricas de negócio. Retirar instâncias por sobrecarga pode agravar o problema se todas ficarem não prontas.
Check de disco
Se a aplicação precisa escrever localmente, verifique capacidade e permissões de forma controlada. Não crie arquivo a cada probe. Uma checagem periódica em background pode atualizar estado.
Check de DNS
Falhas de DNS afetam chamadas externas, mas consultar DNS em toda probe adiciona dependência. Monitore erros reais e use verificação periódica separada.
Check de migração
Readiness pode depender da versão mínima do schema:
const compatible = databaseSchemaVersion >= MIN_SCHEMA_VERSION;Não execute migrations dentro do health endpoint.
Testes
Teste:
- startup antes de ready;
- banco indisponível;
- dependência opcional indisponível;
- timeout;
- shutdown;
- resultado cacheado;
- recuperação após falha;
- oscilações;
- alta frequência de probes.
Observabilidade
Meça duração e resultado dos checks, mudanças de estado, tempo não pronto e motivos. Não use path de probe nas métricas de latência de usuário sem separação, pois ele pode distorcer volume.
Alertas
Alerta deve considerar impacto agregado:
- percentual de réplicas prontas;
- tempo contínuo;
- erro do balanceador;
- SLO de usuário;
- dependência compartilhada;
- reinícios.
Erros comuns
- usar banco na liveness;
- falhar readiness por dependência opcional;
- executar checks caros a cada segundo;
- não definir timeout;
- expor detalhes internos;
- não marcar not-ready no shutdown;
- usar health check para autoscaling;
- oscilar sem hysteresis;
- não testar configuração da plataforma;
- reiniciar por falha que restart não resolve.
Fluxo recomendado
Separe liveness, readiness e startup, mantenha liveness local, classifique dependências, limite custo e integre readiness ao shutdown. Combine com Graceful Shutdown, Event Loop Utilization, Process Reports e métricas de perf_hooks.
Consulte a documentação oficial de probes do Kubernetes e a documentação oficial do servidor HTTP do Node.js.



