HTTP Keep-Alive permite reutilizar conexões TCP para várias requisições. Em clientes Node.js, isso reduz handshakes, criação de sockets e latência, especialmente quando a aplicação chama repetidamente a mesma API. Em servidores, keep-alive mantém conexões abertas por um período para que o cliente envie novas requisições sem reconectar.
A reutilização melhora eficiência, mas exige limites, timeouts e encerramento correto. Configurações incompatíveis entre cliente, proxy e servidor podem causar resets, sockets ociosos demais ou falhas intermitentes.
Por que reutilizar conexões
Cada nova conexão pode envolver:
- resolução DNS;
- handshake TCP;
- handshake TLS;
- negociação de protocolo;
- alocação de socket;
- controle no balanceador.
Quando dezenas de chamadas usam o mesmo destino, manter sockets disponíveis reduz esse custo e evita explosão de conexões de curta duração.
HTTP Agent
O módulo node:http usa agentes para gerenciar sockets. Um agente com keep-alive pode ser criado assim:
import { Agent, request } from 'node:http';
const agent = new Agent({
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 30_000,
});
function getJson(options) {
return new Promise((resolve, reject) => {
const req = request({ ...options, agent }, (res) => {
let body = '';
res.setEncoding('utf8');
res.on('data', (chunk) => body += chunk);
res.on('end', () => resolve(JSON.parse(body)));
});
req.on('error', reject);
req.end();
});
}Para HTTPS, use node:https.Agent. Não reutilize um agente HTTP em chamadas HTTPS.
maxSockets
maxSockets limita sockets simultâneos por origem conforme a estratégia do agente. Um valor muito alto pode sobrecarregar o serviço remoto e o NAT; um valor muito baixo cria fila e aumenta latência.
Dimensione com base em:
- concorrência da aplicação;
- tempo médio da dependência;
- limites do serviço remoto;
- quantidade de réplicas;
- capacidade de portas efêmeras;
- SLO de latência.
maxFreeSockets
Esse valor controla quantos sockets ociosos permanecem no pool. Poucos sockets podem exigir reconexões durante rajadas; muitos sockets consomem recursos e podem ser encerrados pelo servidor remoto antes da próxima utilização.
Timeouts diferentes
Não existe um único timeout. Considere:
- timeout para conectar;
- timeout total da operação;
- timeout de inatividade do socket;
- keep-alive timeout do servidor;
- timeout do proxy;
- deadline da requisição de usuário.
O cliente deve abandonar a chamada antes que o orçamento total expire.
Servidor Node.js
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
});
res.end(JSON.stringify({ status: 'ok' }));
});
server.keepAliveTimeout = 5_000;
server.headersTimeout = 6_000;
server.requestTimeout = 30_000;
server.listen(3000);Mantenha headersTimeout maior que o keep-alive timeout para reduzir corridas. Os valores ideais dependem da versão do Node.js, do proxy e do tráfego.
Proxy e balanceador
O caminho pode envolver cliente, CDN, load balancer, ingress e servidor. Cada camada possui seu próprio timeout. Se o cliente reutiliza uma conexão que o proxy acabou de fechar, pode receber ECONNRESET.
Documente os valores e mantenha uma margem entre eles. Em geral, o cliente deve descartar sockets antes do servidor remoto ou proxy.
Conexões HTTPS
import { Agent } from 'node:https';
const httpsAgent = new Agent({
keepAlive: true,
maxSockets: 100,
maxFreeSockets: 20,
});A reutilização economiza handshakes TLS. Não desative validação de certificado para evitar erros de ambiente. Configure CAs corretamente.
Fetch e Undici
O Fetch do Node.js usa infraestrutura baseada em Undici. Para controle avançado, configure um dispatcher ou pool compatível. Evite criar um novo dispatcher por requisição.
Fila do agente
Quando todos os sockets estão ocupados, novas requisições aguardam. Uma fila ilimitada pode transformar sobrecarga em latência e memória. Aplique:
- limite de concorrência;
- timeout na fila;
- circuit breaker;
- rate limit;
- fallback;
- deadline.
Consumir a resposta
Para que o socket volte ao pool, consuma ou descarte corretamente o corpo da resposta. Abandonar uma resposta no meio pode impedir reutilização.
res.resume();
res.on('end', () => {
// resposta descartada e socket liberado
});Quando o corpo importa, use streams e limites de tamanho.
AbortController
Cancelamento deve destruir ou liberar os recursos associados. Em APIs compatíveis, passe um AbortSignal. Em http.request, também é possível usar signal no objeto de opções.
const signal = AbortSignal.timeout(5_000);
const req = request({
hostname: 'api.internal',
path: '/items',
agent,
signal,
});Retries
Uma conexão reutilizada pode ser fechada pelo remoto entre o empréstimo e a escrita. Uma tentativa de leitura idempotente pode ser repetida com backoff. Não repita automaticamente pagamentos, criação de pedidos ou outras escritas sem idempotency key.
Portas efêmeras
Sem keep-alive, milhares de conexões de curta duração podem levar a muitos sockets em TIME_WAIT e esgotar portas no host ou NAT. Reutilização reduz churn, mas pools excessivos também podem alcançar limites.
DNS e troca de IP
Uma conexão keep-alive continua ligada ao IP resolvido quando foi criada. Mudanças de DNS não movem sockets existentes. Defina vida útil razoável e reciclagem para serviços que alteram endereços com frequência.
Load balancing
Conexões persistentes podem concentrar tráfego em alguns backends. O balanceador distribui conexões, não necessariamente cada requisição. Muitas conexões moderadas costumam distribuir melhor que poucas conexões enormes, mas aumentam custo.
Graceful shutdown do cliente
Ao encerrar a aplicação, destrua o agente depois de concluir chamadas:
await drainRequests();
agent.destroy();Destruir cedo interrompe requisições em andamento.
Graceful shutdown do servidor
Use server.close(), pare readiness e feche conexões ociosas. Conexões keep-alive podem prolongar a drenagem; aplique prazo máximo.
Observabilidade
Monitore:
- sockets ativos;
- sockets livres;
- requisições aguardando;
- conexões criadas;
- reutilização;
- ECONNRESET;
- ETIMEDOUT;
- latência de conexão e total;
- handshakes TLS;
- erros por origem.
Não use hostname dinâmico ou URL completa como label sem normalização.
Teste de carga
Compare keep-alive ativado e desativado com a mesma carga. Meça throughput, p95, p99, conexões por segundo, CPU, sockets, portas e erros. Execute o gerador em outra máquina quando possível.
Erros comuns
- criar Agent por requisição;
- usar maxSockets ilimitado;
- não consumir o corpo;
- configurar timeout incompatível com o proxy;
- não destruir agentes no shutdown;
- repetir escrita após ECONNRESET;
- manter sockets ociosos por tempo excessivo;
- ignorar concentração no load balancer;
- otimizar sem medir fila do pool.
Fluxo recomendado
Crie um agente por origem ou classe de dependência, reutilize-o, limite sockets, consuma respostas e alinhe timeouts com a infraestrutura. Combine com AbortController, Circuit Breaker, Graceful Shutdown e testes com Autocannon.
Consulte a documentação oficial de HTTP Agent e a referência oficial de HTTPS Agent.



