Durante deploy, reinício, escalonamento ou manutenção, o processo Node.js recebe um pedido para encerrar. Se ele termina imediatamente, requisições podem ser interrompidas, mensagens podem ficar sem confirmação e arquivos podem permanecer incompletos. O Graceful Shutdown no Node.js organiza o encerramento para parar novas tarefas, concluir trabalho em andamento e liberar recursos dentro de um limite.
Um desligamento seguro não significa esperar para sempre. O processo precisa de uma janela máxima e uma estratégia para tarefas que não terminam. Também deve lidar com múltiplos sinais, erros fatais e dependências como banco, filas, sockets e workers.
Neste guia, você aprenderá a tratar SIGTERM e SIGINT, fechar servidor HTTP, rastrear conexões, cancelar tarefas, drenar filas, definir timeout forçado e testar o fluxo.
Por que o processo recebe SIGTERM?
Orquestradores e gerenciadores normalmente enviam SIGTERM antes de encerrar o contêiner ou processo. SIGINT é comum quando o desenvolvedor pressiona Ctrl+C. O Node.js permite registrar listeners:
process.on('SIGTERM', () => {
console.log('SIGTERM recebido');
});
process.on('SIGINT', () => {
console.log('SIGINT recebido');
});A documentação oficial de sinais do processo descreve diferenças entre plataformas. Para servidores, consulte a documentação do módulo HTTP.
Veja também o que é Node.js e como criar uma API com Node.js.
Estrutura básica
let shuttingDown = false;
async function shutdown(signal) {
if (shuttingDown) return;
shuttingDown = true;
console.log(`Encerrando por ${signal}`);
try {
await closeResources();
process.exitCode = 0;
} catch (error) {
console.error(error);
process.exitCode = 1;
}
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));A flag impede duas execuções concorrentes. Prefira definir exitCode e permitir que o event loop esvazie, em vez de chamar process.exit() cedo demais.
Fechando o servidor HTTP
function closeServer(server) {
return new Promise((resolve, reject) => {
server.close(error => {
if (error) reject(error);
else resolve();
});
});
}server.close() para novas conexões e aguarda conexões elegíveis. O comportamento com keep-alive depende da versão e das APIs usadas. Versões atuais possuem métodos para fechar conexões ociosas.
Removendo a instância do tráfego
Antes de fechar, marque o serviço como não pronto. O load balancer precisa parar de enviar requisições. Um endpoint de readiness pode mudar de 200 para 503:
let ready = true;
app.get('/ready', (req, res) => {
res.sendStatus(ready ? 200 : 503);
});
async function shutdown() {
ready = false;
await delay(3000);
await closeServer(server);
}O pequeno atraso permite que a mudança se propague. Ajuste conforme o intervalo de health check da infraestrutura.
Rastreando sockets
const sockets = new Set();
server.on('connection', socket => {
sockets.add(socket);
socket.on('close', () => sockets.delete(socket));
});Após a janela de drenagem, destrua conexões remanescentes:
for (const socket of sockets) {
socket.destroy();
}Faça isso apenas depois do prazo, pois destruir imediatamente anula o encerramento gradual.
Timeout global
const forceTimer = setTimeout(() => {
console.error('Timeout do graceful shutdown');
process.exit(1);
}, 30_000);
forceTimer.unref();O timeout deve ser menor que a janela concedida pelo orquestrador. unref() impede que o timer mantenha o processo vivo quando todo o restante já terminou.
Cancelando tarefas internas
Um controlador global comunica encerramento:
const shutdownController = new AbortController();
function beginShutdown() {
shutdownController.abort(
new Error('Aplicação em encerramento')
);
}Propague o sinal para timers, fetch, streams e funções próprias. O guia de AbortController no Node.js explica o padrão.
Fechando banco de dados
await databasePool.end();Pare de aceitar novas consultas antes de fechar o pool. Aguarde transações em andamento, mas aplique um limite. Não encerre a conexão no meio de uma transação sem entender rollback e consistência.
Drenando filas
Consumidores devem parar de reservar novos trabalhos, concluir ou devolver os atuais e fechar a conexão:
await worker.pause();
await worker.close();
await queue.close();A semântica depende da biblioteca. Confirme se close() espera tarefas, cancela ou apenas fecha o cliente.
Streams e arquivos
Espere pipelines em andamento ou cancele com sinal e remova arquivos parciais. Veja Streams no Node.js e File System no Node.js.
WebSocket e conexões longas
Pare upgrades, informe os clientes e feche conexões com código apropriado. Uma janela curta pode ser insuficiente para sessões persistentes; clientes precisam implementar reconexão com backoff.
Worker Threads e processos filhos
Envie mensagem de encerramento cooperativo. Aguarde confirmação e use terminate() ou sinais apenas após timeout. Consulte Worker Threads e Child Process no Node.js.
Ordem de fechamento
Uma ordem comum:
- marcar readiness como falsa;
- parar novas conexões e novas tarefas;
- abortar trabalho não essencial;
- aguardar requisições e jobs;
- fechar filas, banco, cache e telemetria;
- destruir conexões restantes após limite;
- definir código de saída.
A ordem evita fechar o banco enquanto rotas ainda executam consultas.
Erros fatais
uncaughtException e unhandledRejection podem indicar estado inconsistente. Registre o erro, inicie encerramento limitado e permita que o supervisor reinicie. Não continue atendendo indefinidamente após uma exceção desconhecida.
process.on('uncaughtException', error => {
logger.fatal({ error }, 'Exceção não tratada');
shutdown('uncaughtException');
});O handler também pode falhar; mantenha-o simples.
Não chame process.exit() cedo
process.exit() interrompe o event loop e pode cortar logs, stdout e gravações pendentes. Use apenas no timeout forçado ou quando não existe forma segura de continuar.
Telemetria
Antes de terminar, faça flush de logs, métricas e traces com limite. Não permita que uma plataforma de observabilidade indisponível impeça o encerramento.
Idempotência
O encerramento pode receber vários sinais. Cada função de fechamento deve tolerar chamada repetida ou o coordenador deve garantir execução única. Trate erros “já fechado” de forma previsível.
Testando o shutdown
Inicie uma requisição lenta, envie SIGTERM e confirme que:
- readiness fica indisponível;
- novas requisições deixam de chegar;
- a requisição atual termina;
- o banco fecha depois da rota;
- o processo sai dentro do prazo;
- uma tarefa travada é forçada após timeout.
Teste em contêiner e no mesmo gerenciador usado em produção.
Observabilidade
Registre sinal, horário de início, recursos pendentes, duração de cada etapa, timeout e código de saída. Uma métrica de encerramentos forçados revela recursos que não fecham corretamente.
Erros comuns
- Chamar
process.exit()logo após SIGTERM. - Fechar banco antes do servidor.
- Não remover a instância do load balancer.
- Esperar para sempre por uma tarefa.
- Executar shutdown duas vezes.
- Ignorar WebSockets e keep-alive.
- Não fazer flush de telemetria.
- Usar prazo maior que o do orquestrador.
Boas práticas
- Trate SIGTERM e SIGINT.
- Marque readiness como falsa primeiro.
- Pare novas tarefas antes de fechar dependências.
- Use AbortSignal para cancelamento cooperativo.
- Defina timeout global.
- Feche recursos na ordem correta.
- Torne o processo idempotente.
- Monitore encerramentos forçados.
- Teste em ambiente realista.
- Deixe o supervisor reiniciar após erro fatal.
Conclusão
O Graceful Shutdown no Node.js protege requisições, mensagens e dados durante deploys e reinícios. Ele coordena readiness, servidor, tarefas e dependências antes de permitir a saída.
O processo precisa ser gradual, mas limitado. Pare novas entradas, conclua o que for seguro e force o encerramento após o prazo. Com sinais propagados e observabilidade, deploys se tornam previsíveis e deixam de causar falhas intermitentes.




