Verlässlich prüfst du die Verfügbarkeit einer Domain in PHP nur über die Registry oder einen Registrar, also per RDAP, Whois oder Registrar-API. Ein DNS-Lookup mit checkdnsrr() taugt als schneller Vorfilter: Hat die Domain NS-Records, ist sie vergeben. Fehlen sie, kann sie trotzdem registriert, reserviert oder in der Löschphase sein.
Wer zum ersten Mal einen Domain-Check in PHP baut, landet fast immer bei gethostbyname() oder checkdnsrr(). Das funktioniert in der Demo prima und liefert im Betrieb falsche Ergebnisse, sobald jemand eine registrierte, aber nicht konfigurierte Domain eintippt. Das passiert öfter, als man denkt.
Domain-Verfügbarkeit prüfen: welche Quelle sagt die Wahrheit?
Ob eine Domain frei ist, weiß verbindlich nur die Registry der Endung, für .de also die DENIC, für .com Verisign. Alles andere ist abgeleitet. In PHP hast du vier Wege, an diese Information zu kommen, und sie unterscheiden sich stark in Aufwand und Zuverlässigkeit:
| Methode | Aussage bei Treffer | Aussage ohne Treffer | Aufwand |
|---|---|---|---|
DNS (checkdnsrr) | sicher vergeben | unklar | minimal, keine Limits außer dem Resolver |
| RDAP (HTTP/JSON) | sicher vergeben | meist frei (404) | mittel, Bootstrap nötig |
| Whois (Port 43) | sicher vergeben | meist frei, Format je Registry anders | hoch, Parsing pro Endung |
| Registrar-API | frei/vergeben | frei/vergeben | gering, aber Rate-Limits |
Die Registrar-API ist die einzige Quelle, die auch Reservierungen und Sonderfälle kennt, weil sie die Frage so stellt, wie eine echte Registrierung es tun würde. RDAP und Whois sind gute Zweitquellen. DNS ist ein Vorfilter, nicht mehr.
Warum ein DNS-Check keine Verfügbarkeit beweist
Ein DNS-Lookup fragt, ob für einen Namen Records existieren. Eine Domain kann registriert sein, ohne dass sie im DNS auftaucht: Der Inhaber hat nie Nameserver eingetragen, die Domain steckt mitten im Transfer oder die Registry hat sie wegen eines Verfahrens aus der Zone genommen. Bei der DENIC heißt so ein Zustand zum Beispiel, dass die Domain registriert, aber nicht delegiert ist.
Umgekehrt gilt: Hat eine Domain NS- oder SOA-Records, ist sie mit Sicherheit vergeben. Diese Asymmetrie macht DNS trotzdem nützlich. Prüfst du 50 Namensvorschläge, sortiert ein schneller NS-Check meist schon die Hälfte aus, bevor du eine API mit Rate-Limit belastest.
<?php
declare(strict_types=1);
function hasDnsFootprint(string $domain): bool
{
// NS oder SOA vorhanden: Domain ist praktisch sicher registriert
return checkdnsrr($domain, 'NS') || checkdnsrr($domain, 'SOA');
}
Wie dns_get_record() und checkdnsrr() im Detail ticken, welche Record-Typen sie kennen und wo sie scheitern, steht im Artikel zu dns_get_record in PHP.
Domain-Verfügbarkeit per API in PHP abfragen
betadomain bietet einen öffentlichen, lesenden Endpunkt an, der genau diese Frage beantwortet. Kein API-Key, keine Registrierung, ein JSON-Body mit dem Domainnamen. Die Antwort enthält available sowie die Jahrespreise für Registrierung, Transfer und Verlängerung. Die vollständige Beschreibung steht in der Entwickler-Dokumentation von betadomain, maschinenlesbar zusätzlich als OpenAPI 3.1 unter /openapi.json.
<?php
declare(strict_types=1);
function checkViaBetadomain(string $domain): array
{
$ch = curl_init('https://betadomain.de/api/domain/check');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['domain' => $domain], JSON_THROW_ON_ERROR),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false) {
throw new RuntimeException('Netzwerkfehler: ' . curl_error($ch));
}
if ($status === 429) {
throw new RuntimeException('Rate-Limit erreicht, später erneut versuchen');
}
if ($status !== 200) {
throw new RuntimeException("HTTP $status: $body");
}
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
Eine echte Antwort für eine vergebene .de-Domain sieht so aus (Preisfelder gekürzt):
{"domain":"thephpexperts.de","tld":".de","available":false,"registerEur":…,"transferEur":…,"renewEur":…}
Führt betadomain eine Endung nicht, kommt statt available das Feld "supported": false zurück. Das ist kein Fehler, sondern ein ehrliches „weiß ich nicht“. Für .de liegt der Jahrespreis aktuell bei 2,99 €, für .dev bei 22,99 €, Registrierung und Verlängerung kosten jeweils dasselbe.
Mehrere Endungen auf einmal
Für einen Namen ohne Endung gibt es POST /api/domain/search mit {"query": "meinprojekt"}. Der Endpunkt prüft dann parallel .de, .com, .eu, .net, .org, .shop, .io und .dev und liefert ein Array results mit domain, available und priceEur je Eintrag. Für ein Namensfindungs-Tool spart das sieben Requests.
Der komplette Check mit Vorfilter
Zusammengesetzt entsteht eine Funktion mit drei möglichen Ergebnissen: true (frei), false (vergeben) und null (unbekannt). Das dritte Ergebnis ist wichtig. Wer Netzwerkfehler oder nicht geführte Endungen als „frei“ meldet, schickt Nutzer in eine Registrierung, die scheitert.
<?php
declare(strict_types=1);
function isAvailable(string $domain): ?bool
{
$ascii = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
if ($ascii === false) {
throw new InvalidArgumentException("Ungültiger Domainname: $domain");
}
if (hasDnsFootprint($ascii)) {
return false; // spart den API-Call
}
$result = checkViaBetadomain($ascii);
if (($result['supported'] ?? true) === false) {
return null; // Endung wird nicht geführt
}
return $result['available'] ?? null;
}
foreach (['php.net', 'thephpexperts-beispiel-4711.de', 'müller-bäckerei-test-99.de'] as $d) {
var_dump($d, isAvailable($d));
}
Lokal mit PHP 8.5 ausgeführt, kommt genau das heraus, was man erwartet: php.net ist vergeben (schon der DNS-Check schlägt an), die beiden Testnamen sind frei. Die Umlaut-Domain läuft vorher durch idn_to_ascii(). Warum das Flag IDNA_NONTRANSITIONAL_TO_ASCII dabei zählt, erklärt der Beitrag zu Punycode in PHP.
Rate-Limits und Fair Use einplanen
Die öffentliche API erlaubt 30 Anfragen pro Minute und 300 pro sechs Stunden je IP. Bei Überschreitung antwortet sie mit HTTP 429 und einem Retry-After-Header in Sekunden. Für ein Formular auf deiner Website reicht das locker. Für einen Crawler, der nachts 10.000 Namen prüft, nicht, und dafür ist der Endpunkt auch nicht gebaut.
Ein paar Regeln, die sich in der Praxis bewährt haben:
- Ergebnisse cachen. „Vergeben“ darf ruhig 24 Stunden gelten, „frei“ höchstens einige Minuten, weil jemand anderes zuschlagen kann.
- Eingaben im Browser entprellen (Debounce von 300 bis 500 ms), damit nicht jeder Tastendruck einen Request auslöst.
- Den Check serverseitig ausführen, nicht per JavaScript direkt aus dem Browser des Nutzers, sonst teilt sich jedes Büro hinter einer NAT-Adresse dasselbe Limit.
- Bei 429 den Wert aus
Retry-Afterabwarten statt sofort zu wiederholen.
Wenn du lieber direkt bei der Registry nachfragst, sind RDAP-Abfragen in PHP der modernere Weg und Whois per fsockopen der klassische. Beide haben eigene Limits, die von der jeweiligen Registry abhängen und selten dokumentiert sind.
Grenzen jeder Verfügbarkeitsprüfung
Auch die beste Abfrage ist eine Momentaufnahme. Zwischen „frei“ und dem Klick auf „Registrieren“ können Sekunden oder Tage liegen. Dazu kommen Fälle, die keine öffentliche Schnittstelle sauber abbildet:
- Premium-Domains, die die Registry nur zu einem Sonderpreis abgibt
- reservierte Begriffe, etwa Länder- oder Ortsnamen bei manchen Endungen
- Domains in der Redemption- oder Pending-Delete-Phase nach einer Kündigung
- Endungen mit Zugangsbedingungen wie Wohnsitz oder Markennachweis
Deshalb gehört in jedes Interface ein Satz wie „Verfügbarkeit wird bei der Registrierung endgültig bestätigt“. Das ist kein Kleingedrucktes, sondern schlicht die technische Realität.
Wenn du den Check nicht selbst bauen willst und nur wissen möchtest, ob dein Projektname noch zu haben ist: Die Domain-Suche von betadomain nutzt dieselbe Abfrage im Hintergrund. Für eine .dev-Domain lohnt sich vorher ein Blick auf die Infoseite zur Endung .dev, weil .dev nur per HTTPS erreichbar ist.
Schritt für Schritt
- Namen normalisierenLeerzeichen entfernen, kleinschreiben und mit idn_to_ascii() in Punycode umwandeln. Schlägt die Umwandlung fehl, ist der Name ungültig.
- DNS als VorfilterMit checkdnsrr() nach NS- und SOA-Records fragen. Ist einer vorhanden, ist die Domain vergeben und du sparst dir weitere Abfragen.
- Verbindliche Quelle abfragenFür alle übrigen Namen RDAP, Whois oder eine Registrar-API wie POST /api/domain/check aufrufen.
- Fehler und Limits behandelnHTTP 429 mit Retry-After respektieren, Netzwerkfehler als unbekannt werten und nie als frei.
Domain gefunden? Prüfen und registrieren kannst du sie direkt bei Domain registrieren – Jahrespreis ab 2,99 €.
Häufige Fragen
Kann ich mit checkdnsrr() herausfinden, ob eine Domain frei ist?
Nur in eine Richtung. Liefert checkdnsrr($domain, 'NS') true, ist die Domain vergeben. Liefert es false, heißt das lediglich, dass im DNS nichts delegiert ist. Viele registrierte Domains haben keine Nameserver eingetragen, etwa geparkte Bestände oder Domains im Transfer.
Gibt es eine kostenlose API für die Domain-Verfügbarkeit?
Ja, betadomain stellt mit POST /api/domain/check einen öffentlichen Endpunkt ohne API-Key bereit. Er ist pro IP auf 30 Anfragen pro Minute und 300 in sechs Stunden begrenzt. Für Massenprüfungen mit Tausenden Namen ist so eine Fair-Use-API nicht gedacht.
Warum zeigt Whois eine Domain als frei, die ich trotzdem nicht registrieren kann?
Registries reservieren Namen, sperren Begriffe oder halten gelöschte Domains noch in einer Quarantäne. Whois und RDAP kennen diese Zustände nicht immer. Erst der Registrar sieht beim Registrierungsversuch, ob der Name wirklich buchbar ist.
Wie prüfe ich Umlaut-Domains wie müller.de?
Wandle den Namen vorher mit idn_to_ascii() in Punycode um, aus müller.de wird xn--mller-kva.de. DNS, Whois und RDAP arbeiten mit dieser ASCII-Form. Die betadomain-API nimmt auch die Unicode-Schreibweise an, die Umwandlung schadet aber nicht.