dns_get_record in PHP: DNS-Records lesen ohne Fallen

Aktualisiert: 5 Min. Lesezeit Redaktion The PHP Experts
Kurz gesagt

dns_get_record() fragt über den System-Resolver DNS-Records ab und gibt ein Array von Arrays zurück, je Record mit host, class, ttl, type und typabhängigen Feldern wie ip, target oder txt. Existiert ein Name nicht, kommt ein leeres Array. Bei Resolver-Fehlern gibt PHP false und eine Warnung aus. Einen eigenen Nameserver kannst du nicht angeben.

Die PHP-Doku zu dns_get_record() passt auf eine Bildschirmseite, und trotzdem ist es eine der Funktionen, bei denen ich im Code-Review am häufigsten nachfrage. Meist geht es um die gleichen drei Punkte: Was kommt bei einem nicht existierenden Namen zurück, was bei einem Netzwerkproblem, und warum ist DNS_ANY leer.

dns_get_record in PHP: Aufruf und Rückgabe

Die Signatur ist kurz: Hostname, Record-Typ als Bitmaske und zwei optionale Referenzparameter für die Authority- und Additional-Sektion der Antwort.

<?php
declare(strict_types=1);

$records = dns_get_record('php.net', DNS_A | DNS_AAAA);
print_r($records);

Die echte Ausgabe:

Array
(
    [0] => Array
        (
            [host] => php.net
            [class] => IN
            [ttl] => 170
            [type] => A
            [ip] => 185.85.0.29
        )

    [1] => Array
        (
            [host] => php.net
            [class] => IN
            [ttl] => 473
            [type] => AAAA
            [ipv6] => 2a02:cb40:200::1ad
        )

)

Jeder Eintrag hat host, class, ttl und type. Dazu kommen Felder, die vom Typ abhängen. Diese Unterschiede sind der häufigste Grund für Undefined array key-Warnungen.

Record-Typen und ihre Felder

KonstanteTypZusätzliche Felder
DNS_AAip
DNS_AAAAAAAAipv6
DNS_MXMXpri, target
DNS_NSNStarget
DNS_CNAMECNAMEtarget
DNS_TXTTXTtxt, entries
DNS_SOASOAmname, rname, serial, refresh, retry, expire, minimum-ttl
DNS_SRVSRVpri, weight, port, target
DNS_CAACAAflags, tag, value

Bei TXT lohnt ein zweiter Blick. Ein TXT-Record kann aus mehreren Zeichenketten zu je höchstens 255 Bytes bestehen, das ist bei langen SPF- oder DKIM-Einträgen die Regel. entries enthält die Einzelteile, txt die zusammengesetzte Fassung. Für DKIM-Schlüssel nimmst du implode('', $r['entries']), dann bist du auf der sicheren Seite.

Den DMARC-Eintrag einer Domain liest du übrigens nicht an der Domain selbst, sondern an _dmarc.<domain>:

<?php
declare(strict_types=1);

$dmarc = dns_get_record('_dmarc.php.net', DNS_TXT);
echo $dmarc[0]['txt'] ?? 'kein DMARC-Eintrag', "\n";
// v=DMARC1; p=none; rua=mailto:[email protected];

Typen, die PHP nicht kennt, kannst du nicht abfragen. Unter PHP 8.5 gibt es zum Beispiel keine Konstante für HTTPS- oder SVCB-Records, defined('DNS_HTTPS') liefert false.

Leeres Array oder false: was die Rückgabe bedeutet

Das ist der wichtigste Abschnitt. dns_get_record() kennt zwei Arten von „nichts gefunden“, und sie bedeuten etwas völlig Verschiedenes:

SituationRückgabeBedeutung
Name existiert nicht (NXDOMAIN)[]sicher: keine Records
Name existiert, Typ nicht[]sicher: keine Records dieses Typs
Resolver-Fehler, Timeout, SERVFAILfalse plus Warnungunbekannt

Die Warnung lautet bei PHP 8.5 so, hier ausgelöst durch DNS_ALL auf php.net:

PHP Warning:  dns_get_record(): A temporary server error occurred. in Command line code on line 1

Wer das Ergebnis direkt in array_map() oder foreach steckt, bekommt bei false einen TypeError. Prüfe also immer strikt:

<?php
declare(strict_types=1);

/** @return list<array<string, mixed>> */
function lookup(string $host, int $type): array
{
    $records = @dns_get_record($host, $type);
    if ($records === false) {
        $error = error_get_last()['message'] ?? 'unbekannter DNS-Fehler';
        throw new RuntimeException("DNS-Abfrage für $host fehlgeschlagen: $error");
    }
    return $records;
}

Das @ unterdrückt die Warnung im Log, error_get_last() holt die Meldung trotzdem. Alternativ wandelst du Warnungen projektweit per set_error_handler() in Exceptions um.

Warum DNS_ANY und DNS_ALL enttäuschen

DNS_ANY schickt eine ANY-Abfrage. RFC 8482 erlaubt Nameservern seit 2019, darauf nur noch eine minimale Antwort zu geben, und große Anbieter machen davon Gebrauch. Im Test lieferte dns_get_record('cloudflare.com', DNS_ANY) ein leeres Array, für php.net dagegen NS, A, AAAA, MX und TXT. Verlassen kannst du dich darauf nicht.

DNS_ALL fragt die Typen einzeln ab, scheitert aber komplett, wenn einer davon einen Fehler auslöst. Genau das passierte mit php.net. Mein Rat: Frag die Typen ab, die du brauchst, und kombiniere sie mit |.

Grenzen von dns_get_record

Die Funktion nutzt immer den Resolver deines Systems, unter Linux also, was in /etc/resolv.conf steht. Daraus folgen drei Einschränkungen:

  • Du siehst gecachte Daten. Die ttl zählt bei wiederholten Abfragen herunter, und nach einer DNS-Änderung kann der alte Wert noch Stunden zurückkommen.
  • Du kannst keinen Server vorgeben. Für Abfragen direkt am autoritativen Nameserver brauchst du pear/net_dns2 (auf Packagist verfügbar) oder dig @ns1.example.com.
  • Es gibt keinen Timeout-Parameter. Wie lange ein Aufruf hängt, bestimmt der Resolver. Bei einer CAA-Abfrage an einen langsamen Server habe ich beim Testen über eine Minute gewartet.

Für einen Web-Request ist der letzte Punkt heikel. DNS-Abfragen, die nicht zwingend synchron sein müssen, gehören in eine Queue oder einen Cronjob, wie im Beitrag zum DNS-Monitoring mit PHP.

Reverse-DNS und die Parameter authns und addtl

Auch PTR-Records liest du mit dns_get_record(). Dafür drehst du die IPv4-Adresse um und hängst in-addr.arpa an:

<?php
declare(strict_types=1);

$ip  = '185.85.0.29';
$ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa';
print_r(dns_get_record($ptr, DNS_PTR));
// [target] => ip-185-85-0-29.ax5z.com

Für den Einzelfall ist gethostbyaddr() kürzer und liefert denselben Namen. Der Weg über dns_get_record() lohnt sich, wenn du die TTL brauchst oder mehrere PTR-Einträge erwartest.

Die beiden Referenzparameter $authns und $addtl versprechen die Authority- und Additional-Sektion der DNS-Antwort. In der Praxis bleiben sie oft leer, weil moderne Resolver diese Sektionen bei rekursiven Antworten weglassen. Bei meinem Test mit php.net und DNS_MX waren beide Arrays leer. Baue keine Logik darauf, dass dort Glue-Records oder Nameserver auftauchen; frag sie lieber gezielt mit DNS_NS und DNS_A ab.

Typische Anwendungen

Mit dns_get_record() prüfst du, ob eine Domain auf deinen Server zeigt, ob ein Verifizierungs-TXT gesetzt ist oder welche Mailserver zuständig sind. Für Letzteres gibt es Feinheiten wie Null-MX und den A-Record-Fallback, die der Artikel zum MX-Record-Check in PHP behandelt. Und als Vorfilter für Domain-Checks taugt ein NS-Lookup ebenfalls, wie der Beitrag zur Domain-Verfügbarkeit in PHP zeigt.

Umlaut-Domains musst du vorher in Punycode umwandeln. dns_get_record('müller.de', DNS_A) fragt sonst einen Namen ab, den es so im DNS nicht gibt.

Wenn du gerade eine eigene Domain einrichtest und prüfen willst, ob die Records stimmen, liegt der Fehler übrigens oft beim Registrar und nicht im Code. Bei betadomain verwaltest du Nameserver und DNS selbst im Kundenportal; welche Endungen es gibt, zeigt die Übersicht aller Domain-Endungen. Eine .de-Domain kostet dort 2,99 € im Jahr, und mit der Domainsuche prüfst du vorab, ob dein Name frei ist.

Domain gefunden? Prüfen und registrieren kannst du sie direkt bei Domain registrieren – Jahrespreis ab 2,99 €.

Häufige Fragen

Warum liefert dns_get_record mit DNS_ANY nichts zurück?

Viele Nameserver beantworten ANY-Abfragen seit RFC 8482 nur noch minimal oder gar nicht. Bei cloudflare.com kam im Test ein leeres Array zurück. Frag die Typen einzeln oder kombiniert ab, etwa DNS_A | DNS_AAAA | DNS_MX.

Kann ich dns_get_record einen bestimmten Nameserver vorgeben?

Nein, die Funktion nutzt immer den Resolver des Systems. Für Abfragen an einen bestimmten Server, etwa den autoritativen Nameserver oder 1.1.1.1, brauchst du eine Bibliothek wie pear/net_dns2 oder ein externes Werkzeug wie dig.

Was ist der Unterschied zwischen checkdnsrr und dns_get_record?

checkdnsrr() beantwortet nur, ob mindestens ein Record des Typs existiert, und liefert true oder false. dns_get_record() gibt die Records selbst zurück. Für eine reine Existenzprüfung ist checkdnsrr() kürzer, für alles andere brauchst du dns_get_record().

Warum ist die TTL bei jedem Aufruf anders?

PHP fragt den lokalen Resolver, und der gibt bei gecachten Einträgen die Restlaufzeit zurück. Die TTL zählt also herunter. Den Originalwert siehst du nur bei einer Abfrage direkt am autoritativen Server.