mikepultz/netdns2
NetDNS2 is a pure-PHP DNS client supporting record queries, zone transfers, dynamic updates, and DNSSEC validation. It handles all common record types, custom resolvers, and IPv4/IPv6, making DNS lookups, monitoring, and record management easy to add to any PHP or Laravel app.
Install with Composer:
composer require mikepultz/netdns2
There is no Laravel service provider — you instantiate Net_DNS2_Resolver yourself. The first place to look is the Net_DNS2 class (resolver) and the Net_DNS2_Packet_Response object it returns. Resource records come back as typed subclasses of Net_DNS2_RR_* (A, AAAA, MX, CNAME, TXT, SOA, SRV, PTR, NS, etc.). If you need dynamic DNS updates (RFC 2136), look at Net_DNS2_Updater.
Minimal first query:
$resolver = new Net_DNS2_Resolver([
'nameservers' => ['1.1.1.1', '8.8.8.8'],
'timeout' => 2,
'retries' => 2,
]);
try {
$response = $resolver->query('example.com', 'MX');
foreach ($response->answer as $rr) {
echo $rr->preference . ' ' . $rr->exchange . PHP_EOL;
}
} catch (Net_DNS2_Exception $e) {
report($e); // NXDOMAIN, SERVFAIL, timeouts all throw
}
First use case worth wiring up: a small DnsService class that checks MX/SPF/TXT or verifies record propagation, bound as a singleton in a Laravel service provider with your nameservers in config/dns.php.
Resolver as a singleton. Construct the resolver once with your nameserver list, then inject it. In Laravel:
$this->app->singleton(Net_DNS2_Resolver::class, fn () => new Net_DNS2_Resolver([
'nameservers' => config('dns.nameservers'),
'timeout' => config('dns.timeout', 2),
'retries' => config('dns.retries', 2),
]));
Type-stable record access. Use instanceof or the RR type before touching typed properties; answers can contain CNAME chains alongside the type you asked for:
foreach ($response->answer as $rr) {
if ($rr instanceof Net_DNS2_RR_MX) {
$exchanges[] = $rr->exchange;
} elseif ($rr instanceof Net_DNS2_RR_TXT) {
$spf = implode('', (array) $rr->text);
}
}
Property names vary per record: address for A/AAAA, preference/exchange for MX, priority/weight/port/target for SRV, nsdname for NS, ptrdname for PTR, mname/serial/refresh/retry/expire/minimum for SOA.
Dynamic DNS updates (RFC 2136). Use Net_DNS2_Updater against the authoritative server, signing with TSIG:
$updater = new Net_DNS2_Updater('example.com', [
'nameservers' => ['ns1.example.com'],
'tsig_key' => [
'name' => 'ddns-key',
'algorithm' => 'hmac-sha256',
'key' => base64_encode(config('dns.ddns_secret')),
],
]);
$updater->add(new Net_DNS2_RR_A('host.example.com', 300, 'IN', '203.0.113.10'));
$updater->delete(new Net_DNS2_RR_TXT('_acme.example.com', 300, 'IN', 'old-token'));
$updater->update();
Push this into a queued job — updates can be slow and should be retried independently of the HTTP request.
Caching. Enable the built-in file or shared cache for read-heavy lookups:
new Net_DNS2_Resolver([
'nameservers' => ['1.1.1.1'],
'cache_type' => 'file',
'cache_file' => storage_path('framework/cache/dns.cache'),
]);
For high-volume reads, prefer your own Laravel cache layer around query() so you control TTLs and keying rather than relying on the file cache.
DNSSEC verification. Set $resolver->dnssec = true; when you need AD-bit / RRSIG validation, and check the response's authenticated-data status rather than assuming a valid answer.
Command-line / Artisan usage. A dns:check {domain} {type} command that prints answers, $response->answer_from, and timing is a cheap operational tool for propagation checks.
Net_DNS2_Exception. Catch broadly and inspect $e->getCode() (it maps to the DNS RCODE) to distinguish "record doesn't exist" from "resolver is down" — otherwise you'll treat outages as empty results.strict_query_mode defaults to false. CNAME chains and extra records leak into ->answer. Turn it on if you only want the requested type, or filter by instanceof.timeout is generous; set it to 1–2 seconds for anything in the request path, and never loop over domains synchronously in a web request.use_tcp is forced on. This is usually fine, but it means a "quick" UDP lookup can silently become a TCP connection — account for that in latency budgets and firewall rules.idn_to_ascii) the domain before calling query() or you'll get lookups that fail in confusing ways.eval-style parse them.hash_hmac. hmac-sha256/hmac-sha512 need the matching hash algo compiled in; hmac-md5 is the historical default. Base64-encode the shared secret exactly as your DNS provider presents it.Net_DNS2_RR to handle custom/unknown RR types (register the type constant mapping), and subclass Net_DNS2_Cache_File/Shared to plug in Laravel's cache store. Extend Net_DNS2 itself if you need custom socket handling or logging.'debug' => true in the config to print the raw wire packet path; also useful is dumping $response->answer_from, $response->answer_time, and the full ->answer/->authority/->additional arrays.Net_DNS2_* class names and internal style are old-school but run fine on PHP 8. Watch for dynamic-property deprecations on newer PHP if you subclass RRs.How can I help you explore Laravel packages today?