O módulo node:dns oferece APIs para resolver nomes, consultar registros DNS e controlar parte do comportamento de resolução no Node.js. Entender a diferença entre dns.lookup() e métodos como dns.resolve4() é importante para diagnosticar latência, falhas de conexão, IPv6, service discovery e comportamento em containers.
DNS parece uma etapa simples, mas pode dominar o tempo de conexão quando não há cache, o resolvedor está lento ou a aplicação cria conexões continuamente.
lookup e resolve
dns.lookup() usa as facilidades de resolução do sistema operacional, semelhantes ao que outras aplicações usam. Ele pode considerar /etc/hosts, NSS, mDNS e configurações locais.
import dns from 'node:dns/promises';
const result = await dns.lookup('example.com');
console.log(result.address, result.family);Já dns.resolve4() realiza uma consulta DNS para registros A:
const addresses = await dns.resolve4('example.com', {
ttl: true,
});
console.log(addresses);Os dois métodos não são equivalentes. Use lookup quando deseja o comportamento normal do sistema para abrir uma conexão. Use resolve quando precisa consultar registros específicos.
API de Promises
import {
lookup,
resolve4,
resolve6,
resolveMx,
resolveTxt,
reverse,
} from 'node:dns/promises';A API baseada em Promises facilita timeouts e composição, mas a consulta ainda precisa ser limitada e observada.
Consultando IPv4 e IPv6
const ipv4 = await resolve4('example.com');
const ipv6 = await resolve6('example.com');
console.log({ ipv4, ipv6 });Nem todo domínio possui registros AAAA. Trate ausência como resultado possível, não necessariamente como falha geral.
Ordem de resultados
A ordem IPv4 e IPv6 pode afetar conexões. O Node.js oferece configuração para a ordem padrão de resultados de lookup.
import dns from 'node:dns';
dns.setDefaultResultOrder('verbatim');Outra opção comum prioriza IPv4. Escolha com base na rede real e teste dual stack. Não use uma alteração global para esconder falhas de IPv6 sem corrigir a infraestrutura.
lookup com todos os endereços
const addresses = await dns.lookup('example.com', {
all: true,
});
console.log(addresses);Isso ajuda a observar todos os candidatos retornados pelo sistema. A biblioteca de conexão pode aplicar sua própria estratégia.
Registros comuns
- A: endereço IPv4;
- AAAA: endereço IPv6;
- CNAME: alias;
- MX: servidores de email;
- TXT: textos e verificações;
- SRV: serviço, porta e prioridade;
- CAA: autoridades certificadoras permitidas;
- PTR: resolução reversa.
Consultando MX
const mx = await resolveMx('example.com');
mx.sort((a, b) => a.priority - b.priority);
console.log(mx);Consultar MX não confirma que um email existe. Validação de endereço deve evitar chamadas abusivas e não depender de SMTP em tempo real.
Registros SRV
import { resolveSrv } from 'node:dns/promises';
const services = await resolveSrv('_service._tcp.example.com');SRV informa prioridade, peso, porta e alvo. Uma implementação correta precisa respeitar prioridade e distribuição por peso, além de TTL.
Resolver personalizado
import { Resolver } from 'node:dns/promises';
const resolver = new Resolver();
resolver.setServers(['1.1.1.1', '8.8.8.8']);
const addresses = await resolver.resolve4('example.com');Configurar servidores públicos pode quebrar resolução interna, compliance ou política de rede. Em produção, normalmente use o DNS fornecido pela plataforma, VPC ou cluster.
Timeout
As APIs e ambientes podem ter comportamento de timeout diferente. Para impor prazo no nível da aplicação, combine a consulta com controle próprio, sabendo que rejeitar a Promise não necessariamente interrompe toda operação interna.
async function withTimeout(promise, timeoutMs) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('DNS timeout')), timeoutMs),
),
]);
}Prefira APIs que suportem cancelamento real quando disponível.
Erros DNS
Códigos comuns incluem ausência de registro, timeout, falha temporária e servidor indisponível. Não trate todos como 404 ou “host inexistente”.
try {
await resolve4(hostname);
} catch (error) {
logger.warn({
code: error.code,
hostname,
}, 'Falha de DNS');
throw error;
}Evite registrar hostnames fornecidos pelo usuário sem validação, pois podem conter dados sensíveis ou gerar alto volume.
Cache DNS
O Node.js não oferece um cache universal para todas as APIs e clientes. O sistema operacional, a biblioteca HTTP, o proxy ou um cache local podem participar.
Um cache precisa respeitar:
- TTL;
- respostas negativas;
- mudança de IP;
- limite de entradas;
- concorrência;
- falha do resolvedor;
- stale permitido.
Cache simples
const cache = new Map();
async function resolveCached(hostname) {
const now = Date.now();
const cached = cache.get(hostname);
if (cached && cached.expiresAt > now) {
return cached.addresses;
}
const records = await resolve4(hostname, { ttl: true });
const ttl = Math.min(...records.map((item) => item.ttl));
const addresses = records.map((item) => item.address);
cache.set(hostname, {
addresses,
expiresAt: now + ttl * 1000,
});
return addresses;
}Esse exemplo é didático. Em produção, limite tamanho, evite stampede e trate TTL zero.
DNS stampede
Quando uma entrada expira, muitas requisições podem consultar ao mesmo tempo. Mantenha uma Promise em andamento por hostname ou use stale-while-revalidate.
Conexões persistentes
Keep-alive reduz consultas porque sockets são reutilizados. Porém, conexões existentes continuam ligadas ao IP antigo após mudança de DNS. Recicle pools e defina vida útil adequada.
Service discovery
Em Kubernetes, nomes de Services resolvem por DNS. Pods podem mudar, mas o Service oferece um endereço estável. Headless Services retornam múltiplos endpoints e exigem lógica de balanceamento no cliente.
Containers
Verifique /etc/resolv.conf, search domains e ndots. Um nome curto pode gerar várias consultas com sufixos antes de tentar o nome absoluto. Em ambientes com muitos domínios de busca, isso aumenta latência.
FQDN
Um nome totalmente qualificado com ponto final pode evitar aplicação de search domains:
service.namespace.svc.cluster.local.Use apenas quando a plataforma e biblioteca aceitarem o formato.
IPv6 parcial
Um domínio pode retornar AAAA em uma rede sem conectividade IPv6 funcional. O resultado são tentativas lentas ou falhas. Teste o caminho completo, não apenas a consulta DNS.
SSRF e DNS rebinding
Aplicações que buscam URLs fornecidas por usuários precisam proteger contra SSRF. Validar apenas o hostname antes da resolução é insuficiente. Um domínio pode resolver para IP privado ou mudar entre consultas.
Medidas:
- allowlist de destinos;
- validar todos os IPs resolvidos;
- bloquear redes privadas e metadados;
- fixar o IP usado na conexão;
- revalidar redirects;
- limitar portas e protocolos;
- usar proxy de saída controlado.
Observabilidade
Meça:
- duração de resolução;
- erros por código;
- cache hit e miss;
- TTL observado;
- respostas vazias;
- IPv4 e IPv6;
- consultas por hostname normalizado;
- tempo de conexão após DNS.
Não use hostname arbitrário como label de métrica. Agregue por dependência conhecida.
Teste
Teste NXDOMAIN, timeout, múltiplos endereços, IPv6, troca de IP, TTL curto, cache expirado, DNS interno e falha temporária. Em testes unitários, injete uma função resolver em vez de depender da internet.
Erros comuns
- confundir lookup com resolve;
- ignorar TTL;
- criar cache sem limite;
- fixar IP indefinidamente;
- forçar DNS público em rede privada;
- desativar IPv6 sem diagnóstico;
- não proteger contra DNS rebinding;
- usar nome curto com search domains caros;
- não correlacionar DNS e conexão.
Fluxo recomendado
Use lookup para conexão normal e resolve para registros específicos. Respeite TTL, reutilize conexões, proteja destinos externos e monitore a resolução. Combine com HTTP Keep-Alive, AbortController, Circuit Breaker e Rate Limiting.
Consulte a documentação oficial de DNS no Node.js e a especificação do DNS.



