Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Graceful Shutdown no Node.js

Atualizado em: 30 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

Graceful shutdown é o encerramento controlado de uma aplicação Node.js. Em vez de interromper o processo imediatamente, o servidor para de aceitar novas requisições, conclui o trabalho em andamento, fecha conexões e encerra dentro de um prazo conhecido.

Esse comportamento é essencial em deploys, autoscaling, reinícios, manutenção e falhas. Sem shutdown correto, usuários recebem conexões resetadas, transações ficam incompletas, mensagens são perdidas e arquivos podem ser corrompidos.

Sinais principais

Em sistemas Unix e containers, os sinais mais comuns são:

  • SIGTERM: pedido normal de encerramento;
  • SIGINT: interrupção pelo terminal;
  • SIGKILL: encerramento forçado, sem possibilidade de limpeza.

A aplicação deve tratar SIGTERM e SIGINT. SIGKILL não pode ser interceptado.

Servidor HTTP básico

import { createServer } from 'node:http';

const server = createServer(app);
server.listen(3000);

let shuttingDown = false;

async function shutdown(signal) {
  if (shuttingDown) return;
  shuttingDown = true;

  logger.info({ signal }, 'Encerramento iniciado');

  server.close(async (error) => {
    if (error) {
      logger.error({ error }, 'Falha ao fechar servidor');
      process.exitCode = 1;
    }

    await closeResources();
  });
}

process.once('SIGTERM', () => shutdown('SIGTERM'));
process.once('SIGINT', () => shutdown('SIGINT'));

server.close() para de aceitar novas conexões e espera as conexões atendidas conforme o comportamento do servidor e da versão.

Definindo prazo máximo

const FORCE_EXIT_MS = 30_000;

async function shutdown(signal) {
  if (shuttingDown) return;
  shuttingDown = true;

  const forceTimer = setTimeout(() => {
    logger.error({ signal }, 'Shutdown excedeu o prazo');
    process.exit(1);
  }, FORCE_EXIT_MS);
  forceTimer.unref();

  try {
    await stopAcceptingTraffic();
    await drainRequests();
    await closeResources();
    process.exitCode = 0;
  } catch (error) {
    logger.error({ error }, 'Shutdown falhou');
    process.exitCode = 1;
  } finally {
    clearTimeout(forceTimer);
  }
}

O prazo deve ser menor que o tempo concedido pelo orquestrador para evitar SIGKILL antes da limpeza.

Readiness antes do fechamento

Ao iniciar shutdown, marque a aplicação como não pronta. O balanceador deve parar de enviar novas requisições antes de o servidor fechar completamente.

let ready = false;

app.get('/ready', (req, res) => {
  if (!ready || shuttingDown) {
    res.status(503).json({ status: 'not-ready' });
    return;
  }
  res.json({ status: 'ready' });
});

Em Kubernetes, pode existir um pequeno atraso até endpoints serem removidos. Um preStop hook ou período curto de drenagem pode reduzir corridas, mas não substitui o código de shutdown.

Contando requisições ativas

let activeRequests = 0;

app.use((req, res, next) => {
  activeRequests += 1;
  let finished = false;

  function done() {
    if (finished) return;
    finished = true;
    activeRequests -= 1;
  }

  res.once('finish', done);
  res.once('close', done);
  next();
});

Use essa métrica para observar drenagem. Não dependa somente dela para fechar sockets e tarefas fora do HTTP.

Keep-alive

Conexões keep-alive podem prolongar o encerramento. APIs recentes do servidor HTTP oferecem métodos para fechar conexões ociosas e, em último caso, todas as conexões.

server.close();
server.closeIdleConnections?.();

Use fechamento forçado somente após o período de drenagem, pois ele interrompe requisições.

Rastreando sockets

const sockets = new Set();

server.on('connection', (socket) => {
  sockets.add(socket);
  socket.once('close', () => sockets.delete(socket));
});

function destroyRemainingSockets() {
  for (const socket of sockets) socket.destroy();
}

Essa técnica ajuda em versões e protocolos específicos, mas pode interferir com upgrades e HTTP/2. Teste o servidor real.

Banco de dados

Depois de drenar requisições, feche pools:

async function closeResources() {
  await Promise.allSettled([
    database.end(),
    redis.quit(),
    telemetry.shutdown(),
  ]);
}

Fechar o banco cedo demais faz requisições em andamento falharem. A ordem importa.

Filas e consumidores

Um consumidor deve:

  1. parar de buscar novas mensagens;
  2. concluir mensagens em andamento;
  3. confirmar somente as concluídas;
  4. devolver ou deixar expirar as restantes;
  5. fechar conexão.

Defina idempotência porque uma mensagem pode ser reentregue após interrupção.

Tarefas em background

Mantenha um registro de tarefas:

const tasks = new Set();

function track(promise) {
  tasks.add(promise);
  promise.finally(() => tasks.delete(promise));
  return promise;
}

async function drainTasks() {
  await Promise.allSettled([...tasks]);
}

Não aceite tarefas novas quando shuttingDown for verdadeiro.

Cancelando tarefas longas

const shutdownController = new AbortController();

process.once('SIGTERM', () => {
  shutdownController.abort(new Error('Aplicação encerrando'));
  shutdown('SIGTERM');
});

Propague o signal para Fetch, timers, streams e operações próprias. Algumas tarefas devem concluir; outras podem ser canceladas. Defina por categoria.

WebSockets

WebSockets não terminam com uma resposta HTTP comum. Ao encerrar:

  • pare de aceitar upgrades;
  • envie mensagem ou close frame;
  • aguarde prazo curto;
  • termine sockets restantes;
  • preserve estado fora do processo.

Clientes devem reconectar com backoff.

Server-Sent Events

Conexões SSE podem durar horas. Notifique o cliente ou feche de forma controlada, permitindo reconexão por Last-Event-ID quando aplicável.

Worker Threads

O pool deve parar de receber tarefas, aguardar as ativas e encerrar workers:

await workerPool.drain({ timeoutMs: 10_000 });
await workerPool.close();

Se o prazo expirar, defina se tarefas podem ser retomadas em fila externa.

Processos filhos

Envie SIGTERM aos processos gerenciados, aguarde exit e aplique limite:

child.kill('SIGTERM');
setTimeout(() => child.kill('SIGKILL'), 5000).unref();

Considere a árvore de processos e diferenças entre plataformas.

Cluster

O primário coordena workers: deixa de criar novos, envia comando de shutdown e aguarda. Em rolling restart, substitua um worker por vez depois que o novo estiver pronto.

Logs e telemetria

Envie métricas e traces pendentes antes de encerrar. Exportadores possuem métodos de flush e shutdown. Não espere indefinidamente por uma plataforma de observabilidade indisponível.

Ordem recomendada

  1. marcar not-ready;
  2. parar entrada de trabalho;
  3. iniciar prazo máximo;
  4. drenar HTTP e consumidores;
  5. concluir ou cancelar tarefas;
  6. fechar banco e caches;
  7. flush de telemetria;
  8. encerrar workers e filhos;
  9. definir exit code;
  10. deixar o event loop esvaziar.

Evite process.exit cedo

process.exit() encerra imediatamente e pode descartar logs e I/O pendente. Prefira definir process.exitCode e fechar handles. Use exit forçado apenas no timeout final.

Exceções não tratadas

Em uncaughtException, o estado pode estar inconsistente. Faça logging mínimo e shutdown, sem continuar atendendo tráfego:

process.on('uncaughtException', (error) => {
  logger.fatal({ error }, 'Exceção não tratada');
  shutdown('uncaughtException');
});

Também trate rejeições conforme a política do runtime e corrija a causa.

Idempotência do shutdown

Vários sinais e erros podem acionar o encerramento. Proteja com uma flag ou Promise compartilhada para executar uma única vez.

Kubernetes

Alinhe:

  • terminationGracePeriodSeconds;
  • readiness probe;
  • preStop, se usado;
  • timeout interno;
  • duração máxima das requisições;
  • timeout do ingress;
  • tempo de flush.

O timeout interno deve deixar margem antes do SIGKILL.

Docker

Use forma exec no CMD para que o Node.js receba sinais:

CMD ["node", "dist/server.js"]

A forma shell pode criar um processo intermediário. Use init quando a aplicação cria filhos e precisa colher processos.

systemd

Configure o sinal e timeout do serviço de acordo com a aplicação. Não deixe systemd matar antes do prazo interno. O supervisor deve reiniciar processos que encerram com falha.

Testando shutdown

  1. inicie uma requisição lenta;
  2. envie SIGTERM;
  3. confirme que novas requisições são rejeitadas;
  4. confirme conclusão da requisição ativa;
  5. verifique fechamento do banco;
  6. observe exit code;
  7. repita com prazo excedido;
  8. teste WebSocket e consumidores.

Métricas

Registre:

  • shutdowns por motivo;
  • duração total;
  • requisições drenadas;
  • tarefas canceladas;
  • timeouts forçados;
  • erros durante limpeza;
  • sockets restantes;
  • exit code.

Erros comuns

  • chamar process.exit imediatamente;
  • fechar banco antes das requisições;
  • não marcar not-ready;
  • não definir prazo;
  • ignorar keep-alive e WebSockets;
  • não parar consumidores;
  • não propagar cancelamento;
  • executar shutdown várias vezes;
  • usar CMD em forma shell;
  • não testar em ambiente real.

Fluxo recomendado

Implemente shutdown idempotente, marque not-ready, pare entradas, drene trabalho, feche recursos na ordem e aplique prazo. Combine com AbortController, Cluster, child_process e Process Reports.

Consulte a documentação oficial de server.close e a referência oficial de sinais do processo.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita