RDAP in PHP: Domaindaten per JSON statt Whois

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

Für eine RDAP-Abfrage in PHP lädst du die IANA-Bootstrap-Datei data.iana.org/rdap/dns.json, suchst darin den RDAP-Server der Endung und rufst per HTTPS <server>/domain/<name> auf. Die Antwort ist JSON nach RFC 9083. HTTP 404 bedeutet in der Regel: nicht registriert. Für .de fehlt der Eintrag im Bootstrap, dort fragst du rdap.denic.de direkt.

Der erste RDAP-Request, den ich in PHP abgeschickt habe, ging an rdap.org und kam für eine .de-Domain mit einem Fehler zurück. Der Grund ist banal: Die DENIC steht nicht in der Bootstrap-Liste der IANA, und Dienste, die auf dieser Liste aufbauen, kennen .de deshalb nicht. Das ist der erste von mehreren Stolpersteinen, die man bei RDAP in PHP kennen sollte.

Was RDAP besser macht als Whois

RDAP (Registration Data Access Protocol) ist der Nachfolger von Whois, spezifiziert in RFC 9082 (Abfrage) und RFC 9083 (Antwortformat). Statt eines Textblocks auf Port 43 bekommst du strukturiertes JSON über HTTPS. Das hat praktische Folgen:

Whois (Port 43)RDAP
TransportTCP, KlartextHTTPS
Formatfreier Text, je Registry andersJSON, standardisierte Felder
Server findenKonvention oder whois.iana.orgIANA-Bootstrap-Datei
Nicht gefundenTextmuster wie „No match for“HTTP 404
IDNuneinheitlichPunycode in der URL, unicodeName im JSON

Für gTLDs ist RDAP inzwischen Pflicht. Wer heute einen Domain-Lookup neu baut, fängt dort an und fällt nur bei Bedarf auf Whois zurück. Wie dieser Fallback aussieht, steht im Beitrag zu Whois in PHP.

RDAP-Bootstrap: den richtigen Server pro Endung finden

Die IANA veröffentlicht unter https://data.iana.org/rdap/dns.json eine Liste, welche Endung von welchem RDAP-Server bedient wird. Der Aufbau ist schlicht: Unter services stehen Paare aus einer Liste von Endungen und einer Liste von Basis-URLs.

{
  "version": "1.0",
  "publication": "2026-09-30T23:00:03Z",
  "services": [
    [["com"], ["https://rdap.verisign.com/com/v1/"]],
    [["charity", "foundation", "gives", "giving", "ngo", "ong", "org", "xn--c1avg"], ["https://rdap.publicinterestregistry.org/rdap/"]]
  ]
}

Die Datei, die ich für diesen Artikel geladen habe, enthielt 592 Service-Einträge. Was fehlt, ist ebenso aufschlussreich: .de, .eu, .io, .co, .me, .at und .ch tauchten nicht auf. Für diese Endungen musst du die URL selbst kennen oder auf Whois ausweichen. Die DENIC betreibt ihren RDAP-Dienst unter https://rdap.denic.de/, das trägst du als Fallback ein.

Ein RDAP-Client in PHP

Die folgende Klasse lädt den Bootstrap, cacht ihn 24 Stunden im Dateisystem und fragt den passenden Server ab. Getestet mit PHP 8.5 gegen Verisign, PIR und DENIC.

<?php
declare(strict_types=1);

final class RdapClient
{
    private const BOOTSTRAP = 'https://data.iana.org/rdap/dns.json';
    private const FALLBACK  = ['de' => 'https://rdap.denic.de/'];

    /** @var array<string, string>|null */
    private ?array $map = null;

    public function __construct(private string $cacheFile = '/tmp/rdap-dns.json') {}

    public function baseUrl(string $tld): ?string
    {
        if ($this->map === null) {
            $this->map = $this->loadBootstrap();
        }
        return $this->map[$tld] ?? self::FALLBACK[$tld] ?? null;
    }

    /** @return array<string, string> */
    private function loadBootstrap(): array
    {
        $fresh = is_file($this->cacheFile) && filemtime($this->cacheFile) > time() - 86400;
        $json  = $fresh ? file_get_contents($this->cacheFile) : $this->get(self::BOOTSTRAP)[1];
        if ($fresh === false) {
            file_put_contents($this->cacheFile, $json);
        }
        $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

        $map = [];
        foreach ($data['services'] as [$tlds, $urls]) {
            // https bevorzugen, falls mehrere URLs gelistet sind
            $https = array_values(array_filter($urls, fn(string $u) => str_starts_with($u, 'https://')));
            $url   = rtrim($https[0] ?? $urls[0], '/') . '/';
            foreach ($tlds as $tld) {
                $map[strtolower($tld)] = $url;
            }
        }
        return $map;
    }

    /** @return array{0:int,1:string} */
    private function get(string $url): array
    {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_HTTPHEADER     => ['Accept: application/rdap+json'],
            CURLOPT_USERAGENT      => 'rdap-php-example/1.0',
        ]);
        $body   = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        // Manche Server beenden TLS bei 404 ohne close_notify. Dann ist $body false,
        // der Statuscode aber gültig. Nur ohne Statuscode ist es ein echter Netzwerkfehler.
        if ($body === false && $status === 0) {
            throw new RuntimeException(curl_error($ch));
        }
        return [$status, (string) $body];
    }

    public function domain(string $domain): ?array
    {
        $ascii = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
        if ($ascii === false) {
            throw new InvalidArgumentException("Kein gültiger Domainname: $domain");
        }
        $tld  = substr(strrchr($ascii, '.'), 1);
        $base = $this->baseUrl($tld) ?? throw new RuntimeException("Kein RDAP-Server für .$tld bekannt");

        [$status, $body] = $this->get($base . 'domain/' . rawurlencode($ascii));

        return match (true) {
            $status === 404 => null,                       // nicht registriert (meistens)
            $status === 429 => throw new RuntimeException('Rate-Limit des RDAP-Servers'),
            $status >= 400  => throw new RuntimeException("RDAP-Fehler HTTP $status"),
            default         => json_decode($body, true, 512, JSON_THROW_ON_ERROR),
        };
    }
}

Eine Sache in get() sieht übertrieben aus und ist es nicht. Beim Test gegen rdap.verisign.com mit einer nicht existierenden .com-Domain lieferte cURL in PHP zwar den Status 404, aber curl_exec() gab false zurück, mit dieser Meldung:

OpenSSL SSL_read: OpenSSL/4.0.3: error:0A000126:SSL routines::unexpected eof while reading, errno 0

Der Server schließt die Verbindung ohne sauberes TLS-Ende, neuere OpenSSL-Versionen werten das als Fehler. Wer nur auf $body === false prüft, meldet dann „Netzwerkfehler“ statt „nicht registriert“.

Die RDAP-Antwort auswerten

Das JSON ist ausführlich. Für die meisten Zwecke brauchst du fünf Dinge: Status, Registrierungs- und Ablaufdatum, Nameserver und den Registrar. Die Daten stecken in events, der Registrar in entities mit der Rolle registrar als jCard.

<?php
declare(strict_types=1);

function summarize(array $rdap): array
{
    $events = [];
    foreach ($rdap['events'] ?? [] as $e) {
        $events[$e['eventAction']] = $e['eventDate'];
    }
    $registrar = null;
    foreach ($rdap['entities'] ?? [] as $entity) {
        if (in_array('registrar', $entity['roles'] ?? [], true)) {
            $registrar = $entity['handle'] ?? null;
            foreach ($entity['vcardArray'][1] ?? [] as $prop) {
                if ($prop[0] === 'fn') {
                    $registrar = $prop[3];
                }
            }
        }
    }
    return [
        'name'        => $rdap['ldhName'] ?? null,
        'status'      => $rdap['status'] ?? [],
        'registered'  => $events['registration'] ?? null,
        'expires'     => $events['expiration'] ?? null,
        'nameservers' => array_map(fn(array $ns) => rtrim(strtolower($ns['ldhName']), '.'), $rdap['nameservers'] ?? []),
        'registrar'   => $registrar,
    ];
}

Für php.net kam beim Test das hier zurück:

{
    "name": "PHP.NET",
    "status": ["client transfer prohibited", "client update prohibited"],
    "registered": "1997-11-18T05:00:00Z",
    "expires": "2029-11-17T05:00:00Z",
    "nameservers": ["dns1.easydns.com", "dns2.easydns.net", "dns3.easydns.org", "dns4.easydns.info"],
    "registrar": "Tucows Domains Inc."
}

Bei denic.de sieht es anders aus: Status active, vier Nameserver, aber weder Registrierungs- noch Ablaufdatum und kein Registrar. Die DENIC liefert diese Felder nicht. Außerdem schreibt sie die Nameserver mit abschließendem Punkt (ns1.denic.de.), deshalb das rtrim() in der Funktion.

Was eine 404 bei RDAP bedeutet und was nicht

RFC 9082 sieht für ein nicht gefundenes Objekt HTTP 404 vor. Für eine Verfügbarkeitsprüfung ist das ein brauchbares Signal, aber kein endgültiges. Reservierte Namen, Premium-Domains der Registry oder Domains, die gerade gelöscht wurden, können ebenfalls 404 liefern und trotzdem nicht registrierbar sein. Der Beitrag zur Domain-Verfügbarkeit in PHP zeigt, wie du RDAP mit einer Registrar-Abfrage kombinierst.

Zu Rate-Limits schweigen die meisten Registries. Rechne damit, dass ein Server bei mehreren Dutzend Anfragen pro Minute mit 429 antwortet oder Verbindungen kommentarlos abbricht. Cache Ergebnisse und bau bei Massenabfragen Pausen ein.

Statuswerte lesen, ohne sie misszuverstehen

Die Werte in status stammen aus einer festen Liste (RFC 8056 bildet die EPP-Statuscodes auf RDAP ab). Sie werden klein und mit Leerzeichen geschrieben, also client transfer prohibited statt clientTransferProhibited wie im Whois. Ein paar, die dir in der Praxis begegnen:

RDAP-StatusBedeutung
activenormal registriert, keine Sperren
client transfer prohibitedTransfersperre durch den Registrar, üblich und gewollt
server holdvon der Registry aus der Zone genommen, Domain löst nicht auf
redemption periodabgelaufen, Inhaber kann noch zurückkaufen
pending deletewird in Kürze gelöscht und danach wieder frei

Gerade die letzten beiden sind für Verfügbarkeits-Tools interessant. Eine Domain mit pending delete ist nicht frei, wird es aber meist innerhalb weniger Tage. Ein Tool, das nur auf 404 schaut, verpasst diese Information.

Höflich abfragen: User-Agent, Cache, Timeouts

RDAP-Server sind öffentliche Infrastruktur, die Registries ohne Gegenleistung betreiben. Drei Gewohnheiten machen deinen Client zu einem angenehmen Gast. Setz einen aussagekräftigen User-Agent mit Kontaktmöglichkeit, damit ein Betreiber dich erreichen kann, bevor er deine IP sperrt. Cache Antworten pro Domain mindestens eine Stunde, Registrierungsdaten ändern sich selten. Und setz harte Timeouts: Zehn Sekunden sind großzügig, manche Server antworten unter Last gar nicht, und ein hängender PHP-FPM-Worker blockiert deine eigene Anwendung länger als jede fehlende Auskunft.

Auch die notices im JSON lohnen einen Blick. Die DENIC schreibt dort ihre Nutzungsbedingungen hinein: Die Daten dürfen nur für technische oder administrative Zwecke des Internetbetriebs und zur Lösung rechtlicher Probleme verwendet werden, Werbung ist ausdrücklich ausgeschlossen.

RDAP ohne eigenen Code

Falls du nur nicht-personenbezogene Registry-Daten brauchst, gibt es das whois_lookup-Tool im MCP-Server von betadomain. Es macht intern genau diesen Ablauf (IANA-Bootstrap, DENIC-Sonderfall, Punycode) und gibt Status, Daten, Nameserver und Registrar zurück, ohne Inhaberdaten. Wie man es aus PHP oder einem KI-Client anspricht, steht im Beitrag zum MCP-Server für Domains und auf der API-Seite von betadomain.

Wenn deine RDAP-Abfrage für den Wunschnamen eine 404 liefert, ist der nächste Schritt die echte Probe: Namen bei betadomain prüfen und die Preise auf der Seite zur Endung .de ansehen, aktuell 2,99 € im Jahr.

Schritt für Schritt

  1. Bootstrap laden und cachendata.iana.org/rdap/dns.json herunterladen, lokal 24 Stunden cachen und in ein Array Endung zu Basis-URL umbauen.
  2. Domain in Punycode wandelnMit idn_to_ascii() die ASCII-Form erzeugen und die Endung abtrennen.
  3. RDAP-Server abfragenGET <basis>/domain/<ascii-domain> mit Accept: application/rdap+json senden.
  4. Antwort auswerten404 als nicht registriert werten, 429 als Rate-Limit, sonst JSON parsen und events, status und nameservers auslesen.

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

Häufige Fragen

Ersetzt RDAP Whois vollständig?

Für gTLDs wie .com, .org oder .app verlangt die ICANN RDAP schon länger, und seit dem 28. Januar 2025 müssen gTLD-Registries und Registrare kein Whois auf Port 43 mehr anbieten. Bei Länderendungen sieht es gemischt aus: Manche bieten nur Whois, manche beides. In PHP brauchst du deshalb oft beide Wege.

Warum liefert RDAP bei .de keine Inhaberdaten?

Die DENIC gibt über RDAP und Whois grundsätzlich keine Daten zum Domaininhaber heraus. Du bekommst Status, Nameserver und den Zeitpunkt der letzten Änderung. Inhaberauskünfte gibt es nur bei berechtigtem Interesse über das Webformular der DENIC.

Wie oft sollte ich die Bootstrap-Datei neu laden?

Einmal am Tag genügt. Die Datei ändert sich selten und trägt ein Feld publication mit dem Veröffentlichungsdatum. Bei jedem Request neu herunterzuladen kostet nur Zeit und belastet die IANA unnötig.

Gibt es ein Composer-Paket für RDAP?

Für viele Projekte reicht eine eigene Klasse mit cURL wie in diesem Beitrag, weil das Protokoll schlicht HTTP und JSON ist. Wer zusätzlich Whois braucht, findet mit io-developer/php-whois ein gepflegtes Paket auf Packagist, das beides abdeckt.