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

Unix Domain Sockets no Node.js: IPC local

Atualizado em: 9 de outubro de 2026

Rack de servidores processando fluxos de dados no Node.js

Unix Domain Sockets no Node.js permitem comunicação entre processos na mesma máquina sem abrir uma porta TCP. Em vez de usar um endereço como 127.0.0.1:3000, o servidor escuta em um caminho do sistema de arquivos, como /run/minha-api.sock. Essa abordagem é comum em proxies reversos, agentes locais, bancos de dados, ferramentas de linha de comando e serviços executados pelo systemd.

O módulo node:net oferece suporte nativo a IPC por Unix domain sockets em Linux e macOS, além de named pipes no Windows. A API é quase a mesma usada para TCP: você cria um net.Server, escuta em um caminho e conecta com net.createConnection(). A diferença está na forma de endereçamento, permissões, limpeza do arquivo do socket e modelo de segurança.

Neste guia, você aprenderá a criar servidor e cliente, definir protocolo de mensagens, lidar com backpressure, permissões, reinicializações, systemd, containers, testes e compatibilidade com Windows.

Quando usar Unix domain sockets?

Use quando os processos estão no mesmo host e você deseja:

  • evitar exposição de uma porta de rede;
  • delegar acesso por permissões de arquivo;
  • conectar Nginx ou outro proxy à aplicação;
  • criar um canal de controle local;
  • comunicar um worker com um agente;
  • reduzir configuração de host e porta;
  • isolar serviços internos da interface de rede.

Unix sockets não substituem TCP quando consumidores estão em máquinas diferentes. Também não eliminam a necessidade de autenticação em cenários com múltiplos usuários ou containers compartilhados.

Servidor básico

import net from 'node:net';
import { unlink } from 'node:fs/promises';

const socketPath = '/tmp/minha-api.sock';

try {
  await unlink(socketPath);
} catch (error) {
  if (error.code !== 'ENOENT') throw error;
}

const server = net.createServer((socket) => {
  socket.setEncoding('utf8');

  socket.on('data', (data) => {
    socket.write(JSON.stringify({ ok: true, echo: data.trim() }) + '\n');
  });

  socket.on('error', (error) => {
    console.error('Erro no cliente IPC', error);
  });
});

server.listen(socketPath, () => {
  console.log(`Servidor ouvindo em ${socketPath}`);
});

O arquivo do socket aparece no sistema de arquivos. Se o processo terminar de forma abrupta, esse arquivo pode permanecer e causar EADDRINUSE na próxima inicialização. Por isso, a rotina remove apenas o caminho esperado antes de escutar.

Cliente básico

import net from 'node:net';

const client = net.createConnection('/tmp/minha-api.sock');
client.setEncoding('utf8');

client.on('connect', () => {
  client.write('olá\n');
});

client.on('data', (data) => {
  console.log(JSON.parse(data));
  client.end();
});

client.on('error', (error) => {
  console.error('Falha na conexão IPC', error);
});

O socket é um stream duplex. Isso significa que não há fronteiras automáticas de mensagem. Um evento data pode conter metade de uma mensagem, uma mensagem completa ou várias mensagens concatenadas.

Defina um protocolo de framing

Uma solução simples é JSON por linha:

function createLineParser(onMessage) {
  let buffer = '';

  return chunk => {
    buffer += chunk;

    while (true) {
      const index = buffer.indexOf('\n');
      if (index === -1) return;

      const line = buffer.slice(0, index);
      buffer = buffer.slice(index + 1);

      if (line.length > 0) onMessage(JSON.parse(line));
    }
  };
}

Defina também um tamanho máximo para impedir que um cliente envie dados infinitos sem o delimitador:

const MAX_BUFFER = 64 * 1024;

if (buffer.length > MAX_BUFFER) {
  socket.destroy(new Error('Mensagem muito grande'));
}

Backpressure

socket.write() pode retornar false quando o buffer interno está cheio. Nesse caso, espere o evento drain:

import { once } from 'node:events';

async function writeMessage(socket, payload) {
  const data = JSON.stringify(payload) + '\n';

  if (!socket.write(data)) {
    await once(socket, 'drain');
  }
}

Ignorar backpressure permite crescimento de memória quando o cliente lê lentamente. O princípio é o mesmo explicado em Streams e Backpressure no Node.js.

Permissões do socket

Depois de criar o socket, restrinja permissões:

import { chmod } from 'node:fs/promises';

server.listen(socketPath, async () => {
  await chmod(socketPath, 0o660);
});

O diretório também precisa ter proprietário e permissões corretos. Prefira /run/nome-do-servico para serviços gerenciados, evitando diretórios graváveis por qualquer usuário.

Não use writableAll ou permissões 0777 como solução rápida. Qualquer usuário local poderia enviar comandos ao serviço.

Autorização do cliente

Permissões de arquivo reduzem a superfície, mas talvez não sejam suficientes. Em Linux, aplicações avançadas podem verificar credenciais do processo no outro lado por APIs nativas ou por um proxy que aplique política. Outra opção é exigir um token efêmero no primeiro frame.

Não envie segredos permanentes em logs. Para armazenamento e rotação, consulte Secret Management no Node.js.

Timeouts

socket.setTimeout(10_000, () => {
  socket.destroy(new Error('Cliente inativo'));
});

O evento de timeout não fecha automaticamente a conexão. A aplicação precisa chamar end() ou destroy(). Defina também deadline por requisição para evitar clientes que mantêm a conexão viva sem concluir uma operação.

Limite de conexões

server.maxConnections = 200;

server.on('drop', () => {
  metrics.ipcConnectionsDropped.inc();
});

O limite protege descritores de arquivo e memória. Monitore conexões ativas, bytes recebidos, bytes enviados, timeouts e erros de protocolo.

Graceful shutdown

const sockets = new Set();

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

async function shutdown() {
  server.close();

  for (const socket of sockets) {
    socket.end();
  }

  await Promise.race([
    new Promise(resolve => server.once('close', resolve)),
    new Promise(resolve => setTimeout(resolve, 10_000))
  ]);

  for (const socket of sockets) socket.destroy();
}

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

Uma rotina completa é apresentada em Graceful Shutdown no Node.js.

Limpeza do arquivo

Quando net.createServer() cria o socket, o Node.js normalmente remove o caminho no fechamento normal. Em crashes, o arquivo pode permanecer. Antes de remover, confirme que não existe um servidor ativo:

  1. tente conectar;
  2. se a conexão funcionar, outro processo está usando o socket;
  3. se retornar ECONNREFUSED, remova o arquivo antigo;
  4. se retornar EACCES, não remova: corrija permissões.

Nunca aplique unlink em um caminho construído com entrada do usuário.

Socket abstrato no Linux

Linux suporta sockets abstratos iniciados por byte nulo:

const socketPath = '\0minha-api';
server.listen(socketPath);

Eles não aparecem no sistema de arquivos e desaparecem quando todas as referências são fechadas. A desvantagem é não usar permissões de arquivo e não funcionar em macOS. Use apenas quando a portabilidade não é necessária.

Windows named pipes

No Windows, o equivalente é um named pipe:

const pipePath = String.raw`\\.\pipe\minha-api`;
server.listen(pipePath);

Crie uma função por plataforma:

import path from 'node:path';

const ipcPath = process.platform === 'win32'
  ? String.raw`\\.\pipe\minha-api`
  : path.join('/tmp', 'minha-api.sock');

Nginx e proxy reverso

Nginx pode encaminhar HTTP para um Unix socket quando a aplicação usa servidor HTTP sobre o handle local. Isso evita uma porta TCP no host. Garanta que o usuário do proxy tenha acesso ao diretório e ao socket.

Para comunicação externa com TLS, mantenha a terminação no proxy e revise TLS e HTTPS no Node.js.

systemd

O systemd pode criar o socket antes da aplicação com socket activation. Isso permite aceitar conexões enquanto o serviço inicia e facilita reinícios. Em configurações simples, também pode criar o diretório de runtime:

[Service]
RuntimeDirectory=minha-api
User=minha-api
Group=www-data
ExecStart=/usr/bin/node /opt/minha-api/server.js

Veja systemd com Node.js para unit files, usuários e hardening.

Containers

Para compartilhar o socket entre containers, monte um volume em ambos:

services:
  api:
    volumes:
      - ipc:/run/minha-api
  proxy:
    volumes:
      - ipc:/run/minha-api

volumes:
  ipc:

Valide UID, GID e permissões. Em Kubernetes, um volume emptyDir compartilhado entre containers do mesmo Pod pode cumprir a mesma função.

Testes

Use diretório temporário e caminho exclusivo:

import { mkdtemp, rm } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';

const dir = await mkdtemp(path.join(os.tmpdir(), 'ipc-test-'));
const socketPath = path.join(dir, 'server.sock');

try {
  // inicia servidor e executa testes
} finally {
  await rm(dir, { recursive: true, force: true });
}

Teste mensagens fragmentadas, múltiplas mensagens no mesmo chunk, cliente lento, payload grande, desconexão abrupta, timeout e reinício com arquivo antigo.

Observabilidade

Registre eventos estruturados sem incluir payloads sensíveis:

  • conexão aberta e fechada;
  • tipo de operação;
  • duração;
  • bytes;
  • código de erro;
  • timeout;
  • fila de escrita.

Use logs com redaction, como descrito em Pino no Node.js.

Erros comuns

  • assumir que cada evento data é uma mensagem;
  • não limitar tamanho do buffer;
  • usar diretório público com permissões amplas;
  • remover um socket ativo;
  • ignorar socket.write() === false;
  • não fechar conexões no shutdown;
  • usar caminho maior que o limite do sistema;
  • não tratar Windows named pipes;
  • confiar somente no fato de ser comunicação local.

Conclusão

Unix Domain Sockets no Node.js oferecem um canal IPC eficiente e simples para processos no mesmo host. A API do módulo node:net é semelhante a TCP, mas o projeto precisa tratar framing, permissões, limpeza do caminho, backpressure e shutdown.

Use diretórios privados, protocolo com limites, timeouts e observabilidade. Quando integrado a systemd, Nginx ou containers, o Unix socket reduz portas expostas e cria uma fronteira local clara sem sacrificar confiabilidade.

Consulte a documentação do módulo net do Node.js e a página unix(7) do Linux.

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