Hinter jeder Domain steckt ein Satz von Einträgen, der festlegt, wohin Anfragen gehen und wer die Post entgegennimmt. Mit PHP dns_get_record lässt sich dieser Satz direkt aus dem Skript heraus auslesen, ohne Umweg über die Kommandozeile und ohne externen Dienst.
Am häufigsten wird die Funktion für eine ganz bestimmte Frage eingesetzt, und mit der fängt dieses Tutorial an.
Der häufigste Anlass: existiert diese E-Mail-Domain?
Ein Anmeldeformular prüft die eingegebene Adresse meist nur auf die Schreibweise. Ein Tippfehler in der Domain kommt damit ungehindert durch, und die Bestätigungsmail landet im Nirgendwo. Genau diese Lücke schliesst PHP dns_get_record, weil die Abfrage prüft, ob die Domain überhaupt Post empfangen kann.
<?php
function domainKannMailEmpfangen(string $email): bool
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false;
}
$domain = substr(strrchr($email, '@'), 1);
/* MX bevorzugt, A als zulaessiger Rueckfall laut Standard */
$eintraege = @dns_get_record($domain, DNS_MX | DNS_A);
if ($eintraege === false) {
/* Abfrage fehlgeschlagen, nicht als ungueltig werten */
return true;
}
return $eintraege !== [];
}
var_dump(domainKannMailEmpfangen('info@php-space.info')); /* true */
var_dump(domainKannMailEmpfangen('kunde@tippfehler.test')); /* false */
Zwei Entscheidungen in diesem Beispiel sind wichtiger als der Rest. Erstens wird nicht nur auf DNS_MX geprüft, sondern auch auf DNS_A. Fehlt der MX-Eintrag, darf die Zustellung laut Standard auf die normale Adresse der Domain ausweichen. Wer nur MX abfragt, sperrt kleine, korrekt eingerichtete Domains aus.
Zweitens gilt eine fehlgeschlagene Abfrage nicht als ungültige Adresse. Wenn der eigene Nameserver gerade nicht antwortet, ist das kein Fehler des Nutzers. Die Anmeldung deshalb abzulehnen wäre der schlechtere Weg. Die Formatprüfung davor übernimmt FILTER_VALIDATE_EMAIL, ausführlich beschrieben im Tutorial zu filter_validate_email und _url. Wer den Hostnamen stattdessen aus einer URL herauslösen muss, findet die Muster dazu unter Host mit preg_match() prüfen.
Eine Einschränkung gehört an diese Stelle, nicht in eine Fussnote: ein vorhandener MX-Eintrag beweist nur, dass die Domain Mail annehmen kann. Ob das konkrete Postfach existiert, sagt er nicht. Wer mehr Sicherheit braucht, kommt um eine Bestätigungsmail nicht herum.
Die Record-Typen von PHP dns_get_record()
Der zweite Parameter legt fest, welche Art von Eintrag gesucht wird. PHP dns_get_record kennt dafür eine Reihe von Konstanten, von denen im Alltag etwa sechs gebraucht werden.
| Konstante | Typ | Wofür |
DNS_A | A | IPv4-Adresse der Domain |
DNS_AAAA | AAAA | IPv6-Adresse der Domain |
DNS_MX | MX | Zuständige Mailserver samt Priorität |
DNS_TXT | TXT | Freitext, in der Praxis SPF, DKIM und Nachweise |
DNS_CNAME | CNAME | Verweis auf einen anderen Namen |
DNS_NS | NS | Zuständige Nameserver der Zone |
Mehrere Typen lassen sich mit dem senkrechten Strich verbinden, wie schon im ersten Beispiel zu sehen war. Es gibt zwar auch DNS_ALL und DNS_ANY, doch beide sind mit Vorsicht zu geniessen: viele Nameserver beantworten solche Sammelanfragen nur teilweise, und das Ergebnis unterscheidet sich je nach Betriebssystem. Eine gezielte Verknüpfung ist verlässlicher.
<?php
$typen = DNS_A | DNS_MX | DNS_TXT | DNS_NS;
$eintraege = dns_get_record('php-space.info', $typen);
$nachTyp = [];
foreach ($eintraege as $eintrag) {
$nachTyp[$eintrag['type']][] = $eintrag;
}
foreach ($nachTyp as $typ => $liste) {
printf("%s: %d Eintraege\n", $typ, count($liste));
}
Damit stellt sich die Frage, wie die zurückgegebenen Daten überhaupt aufgebaut sind.
Wie das Rückgabearray von PHP dns_get_record() aufgebaut ist
PHP dns_get_record liefert ein Array von Arrays. Jeder Eintrag beschreibt einen einzelnen Record, und vier Felder sind dabei immer vorhanden: host, class, ttl und type. Alles Weitere hängt vom Typ ab, und genau dort verlieren die meisten den Faden.
| Typ | Zusätzliche Felder | Bedeutung |
| A | ip | Die IPv4-Adresse als Zeichenkette |
| AAAA | ipv6 | Die IPv6-Adresse |
| MX | target, pri | Mailserver und Priorität, kleinster Wert zuerst |
| TXT | txt, entries | Gesamttext und die einzelnen Teilstücke |
| CNAME, NS | target | Der Name, auf den verwiesen wird |
Die MX-Abfrage zeigt das Muster am deutlichsten, weil dort mit pri ein Feld dazukommt, das es sonst nirgends gibt.
<?php
$eintraege = dns_get_record('php-space.info', DNS_MX);
/* Kleinste Prioritaet zuerst, so wie zugestellt wird */
usort($eintraege, fn($a, $b) => $a['pri'] <=> $b['pri']);
foreach ($eintraege as $eintrag) {
printf(
"%s (Prioritaet %d, TTL %d)\n",
$eintrag['target'],
$eintrag['pri'],
$eintrag['ttl']
);
}
Bei TXT-Einträgen gibt es eine Besonderheit, die regelmässig für Verwirrung sorgt. Lange Einträge werden im DNS in Teilstücke zerlegt, und deshalb liefert die Funktion zwei Felder: txt mit dem zusammengesetzten Wert und entries mit den einzelnen Teilen.
<?php
$eintraege = dns_get_record('php-space.info', DNS_TXT);
foreach ($eintraege as $eintrag) {
echo $eintrag['txt'] . "\n";
foreach ($eintrag['entries'] as $teil) {
echo ' Teil: ' . $teil . "\n";
}
if (str_starts_with($eintrag['txt'], 'v=spf1')) {
echo ' Das ist der SPF-Eintrag' . "\n";
}
}
Sobald die Struktur klar ist, bleibt der Umgang mit dem Fall, dass nichts zurückkommt.
PHP dns_get_record(): leeres Array oder false
Hier steckt der Fehler, der in fast jedem Codebeispiel im Netz zu finden ist. PHP dns_get_record hat zwei verschiedene Arten, nichts zu liefern, und sie bedeuten Gegensätzliches. Ein leeres Array heisst: die Abfrage lief sauber, es gibt nur keinen Eintrag dieses Typs. Ein false heisst: die Abfrage selbst ist gescheitert, über die Domain wissen wir gar nichts.
<?php
$ergebnis = @dns_get_record('beispiel-ohne-mx.test', DNS_MX);
if ($ergebnis === false) {
echo 'Abfrage fehlgeschlagen, Zustand unbekannt';
} elseif ($ergebnis === []) {
echo 'Domain hat keinen MX-Eintrag';
} else {
echo 'Domain hat ' . count($ergebnis) . ' MX-Eintraege';
}
Wer beide Fälle gleich behandelt, erklärt bei jeder Netzstörung reihenweise gültige Adressen für ungültig. Das vorangestellte Klammeraffen-Zeichen unterdrückt dabei nur die Warnung, die PHP bei fehlgeschlagener Abfrage ausgibt. Der Rückgabewert wird trotzdem geprüft, und genau so gehört es gemacht.
Die Grenzen von PHP dns_get_record()
Die wichtigste Einschränkung steht in keinem Parameter: die Funktion kennt keinen Timeout. Antwortet ein Nameserver nicht, wartet der Aufruf, und das lässt sich von aussen nicht begrenzen. In einer Schleife über hunderte Domains summiert sich das zu Minuten.
Der einzige praktikable Umgang damit ist, Ergebnisse zwischenzuspeichern. DNS-Daten ändern sich selten, und jeder Eintrag bringt mit ttl sogar seine eigene Gültigkeitsdauer mit.
<?php
function mxMitCache(string $domain, int $dauer = 3600): array
{
$datei = sys_get_temp_dir() . '/mx_' . md5($domain) . '.json';
if (is_readable($datei) && filemtime($datei) > time() - $dauer) {
return json_decode((string) file_get_contents($datei), true);
}
$eintraege = @dns_get_record($domain, DNS_MX) ?: [];
file_put_contents($datei, json_encode($eintraege));
return $eintraege;
}
Zwei weitere Punkte sind schnell gesagt, kosten aber ohne Vorwarnung Zeit.
Kein Protokoll, kein Pfad Der erste Parameter ist ein Hostname, keine URL. https://example.com/ liefert nichts. Wer aus einer URL kommt, zerlegt sie vorher mit parse_url().
Ein MX-Eintrag ist kein Postfach Die Abfrage sagt, dass die Domain Mail annimmt. Ob die Adresse davor existiert, bleibt offen. Als Filter gegen Tippfehler ist die Prüfung stark, als Nachweis taugt sie nicht.
PHP dns_get_record(), checkdnsrr() und gethostbyname()
PHP bringt drei Funktionen mit, die sich überschneiden. checkdnsrr() beantwortet nur die Ja-Nein-Frage, ob ein Eintrag existiert. gethostbyname() liefert genau eine IPv4-Adresse und sonst nichts. PHP dns_get_record ist die vollständige Auskunft und kann beides ersetzen, kostet dafür etwas mehr Code.
Als Faustregel: geht es nur um die Existenz eines MX-Eintrags, genügt checkdnsrr(). Braucht man die Server selbst, ihre Priorität oder die Gültigkeitsdauer, führt kein Weg an der ausführlichen Variante vorbei. Wer ohnehin schon HTTP-Antworten prüft, findet mit PHP get_headers() das passende Gegenstück für die Ebene darüber.
Der folgende Ablauf zeigt, wie eine saubere Abfrage von der Eingabe bis zum Ergebnis aussieht.
flowchart TD
A[Domain aus Eingabe] --> B{Format gueltig?}
B -->|Nein| C[Ablehnen]
B -->|Ja| D[dns_get_record aufrufen]
D --> E{Rueckgabe}
E -->|false| F[Abfrage fehlgeschlagen]
E -->|leeres Array| G[Kein Eintrag vorhanden]
E -->|Eintraege| H[Felder je Typ auslesen]
H --> I[Ergebnis zwischenspeichern]
F --> J[Nicht als ungueltig werten]
Fazit
PHP dns_get_record macht das Domain Name System aus dem Skript heraus zugänglich. Der häufigste Einsatz ist die Prüfung von E-Mail-Domains, und dort liefert die Funktion einen starken Filter gegen Tippfehler, ohne echte Nutzer auszusperren, solange auch der A-Record als Rückfall gilt.
Drei Dinge entscheiden darüber, ob der Einsatz trägt. Erstens die Unterscheidung zwischen leerem Array und false, weil beides Verschiedenes bedeutet. Zweitens das Wissen, dass die Felder je Record-Typ unterschiedlich heissen. Und drittens ein Zwischenspeicher, sobald mehr als eine Handvoll Domains geprüft wird, denn einen Timeout gibt es nicht.