Domain validieren mit Regex in PHP: Muster und Tests

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

Ein Domainname besteht aus Labels von 1 bis 63 Zeichen aus a–z, 0–9 und Bindestrich, die nicht mit einem Bindestrich beginnen oder enden, getrennt durch Punkte, insgesamt höchstens 253 Zeichen. In PHP prüfst du das mit einem Regex plus Lookahead für die Gesamtlänge. Umlaut-Domains wandelst du vorher mit idn_to_ascii() in Punycode um.

Der Regex für Domainnamen, den man auf Stack Overflow zuerst findet, ist meistens entweder zu streng oder zu großzügig. Zu streng heißt: müller.de und xn--mller-kva.de fallen durch. Zu großzügig heißt: -start.de oder ein Label mit 70 Zeichen gehen durch. Beides landet früher oder später als Bug-Report im Tracker.

Welche Regeln gelten für Domainnamen?

Die Regeln stammen aus RFC 1035 und RFC 1123 und sind überschaubar. Für Namen, die im Internet registriert und aufgerufen werden, gilt:

RegelWertQuelle
Zeichen pro Labela–z, 0–9, BindestrichRFC 1035, gelockert durch RFC 1123
Länge pro Label1 bis 63 ZeichenRFC 1035
Gesamtlängehöchstens 253 Zeichen (ohne abschließenden Punkt)RFC 1035, 255 Oktette im Wire-Format
Bindestrichnicht am Anfang oder Ende eines LabelsRFC 1035
Position 3 und 4-- reserviert für xn-- und künftige PräfixeRFC 5891
Groß-/KleinschreibungegalRFC 4343

Umlaute stehen nicht in der Tabelle, weil sie im DNS gar nicht vorkommen. müller.de heißt dort xn--mller-kva.de. Ein Regex prüft also sinnvollerweise die ASCII-Form, und du wandelst vorher um. Wie das funktioniert, beschreibt der Beitrag zu Punycode in PHP.

Der Regex Schritt für Schritt

<?php
declare(strict_types=1);

const DOMAIN_REGEX = '/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{1,59})$/i';

Die Teile im Einzelnen:

  • (?=.{1,253}$) ist ein Lookahead am Anfang. Er prüft die Gesamtlänge, ohne Zeichen zu verbrauchen.
  • [a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])? ist ein Label: erstes und letztes Zeichen alphanumerisch, dazwischen bis zu 61 Zeichen inklusive Bindestrich. Zusammen höchstens 63.
  • \. nach jedem Label, + für mindestens eines. Damit braucht der Name mindestens einen Punkt.
  • (?:[a-z]{2,63}|xn--[a-z0-9-]{1,59}) ist die Endung: zwei bis 63 Buchstaben oder eine IDN-Endung wie xn--p1ai für .рф.

Die Endung darf keine Ziffern enthalten, außer in der xn---Form. Das ist strenger als der Standard, entspricht aber der Realität: Keine existierende Top-Level-Domain besteht aus Ziffern, und ein Muster, das example.123 akzeptiert, lässt auch IP-Adressen durch.

Die vollständige Prüffunktion

Der Regex allein deckt zwei Dinge nicht ab: Unicode-Eingaben und die reservierte Position 3/4. Beides erledigt eine kleine Funktion drumherum.

<?php
declare(strict_types=1);

function isValidDomain(string $input): bool
{
    $domain = rtrim(strtolower(trim($input)), '.');   // FQDN-Punkt am Ende tolerieren

    // Umlaute erst in Punycode wandeln, dann prüfen
    if (preg_match('/[^\x20-\x7e]/', $domain)) {
        $domain = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
        if ($domain === false) {
            return false;
        }
    }
    if (preg_match(DOMAIN_REGEX, $domain) !== 1) {
        return false;
    }
    // Bindestriche an Position 3 und 4 sind für xn-- reserviert (RFC 5891)
    foreach (explode('.', $domain) as $label) {
        if (substr($label, 2, 2) === '--' && str_starts_with($label, 'xn--') === false) {
            return false;
        }
    }
    return true;
}

trim() und rtrim(..., '.') sind eine bewusste Entscheidung für Nutzereingaben. Leerzeichen am Rand kommen beim Kopieren ständig vor, und ein Punkt am Ende ist technisch sogar die korrekte, vollqualifizierte Schreibweise. Wenn du solche Eingaben lieber ablehnen willst, nimm die beiden Aufrufe heraus.

Regex gegen filter_var(): 17 Testfälle

PHP bringt mit FILTER_VALIDATE_DOMAIN und FILTER_FLAG_HOSTNAME eine eingebaute Prüfung mit. Ich habe beide gegen dieselben Eingaben laufen lassen, Ausgabe unter PHP 8.5:

EingabeisValidDomain()filter_var()
example.comjaja
sub.example.co.ukjaja
müller.dejanein
EXAMPLE.COM.jaja
xn--mller-kva.dejaja
-start.deneinnein
ende-.deneinnein
a..deneinnein
localhostneinja
1.2.3.4neinja
ab--cd.deneinja
a.bneinja
64 × a + .deneinnein
foo_bar.deneinnein
example.123neinja
␣example.com␣ (mit Leerzeichen)janein
example.xn--p1aijaja

filter_var() prüft Hostnamen, keine Internet-Domains. Deshalb gehen localhost, 1.2.3.4 und a.b durch. Das ist für die Validierung eines Servernamens in einer Konfigurationsdatei sinnvoll, für ein Feld „Deine Domain“ nicht. Umgekehrt scheitert filter_var() an Umlauten, weil es kein IDN kennt.

Was ein Regex nicht leisten kann

Der Regex sagt dir, ob etwas wie ein Domainname aussieht. Er sagt nicht, ob die Endung existiert, ob die Domain registriert ist oder wo die registrierbare Grenze liegt. Für die ersten beiden Fragen brauchst du Daten von außen:

  • Ob die Endung existiert, steht in der Root-Zone der IANA. Wenn du nur Endungen akzeptieren willst, die du verkaufst oder unterstützt, gleiche mit deiner eigenen Liste ab.
  • Ob die Domain registriert ist, beantwortet eine Abfrage bei Registry oder Registrar, siehe Domain-Verfügbarkeit mit PHP prüfen.
  • Welcher Teil von shop.example.co.uk die eigentliche Domain ist, verrät nur die Public Suffix List in PHP.

Ein häufiger Fehler ist, einen Regex für alle drei Fragen zu missbrauchen, etwa mit einer fest eingebauten Liste von Endungen im Muster. Die wird nie aktualisiert, und zwei Jahre später lehnt dein Formular .app oder .dev ab.

Wo du den Regex einsetzt

Im Formular prüfst du die Form, bevor eine API oder ein DNS-Lookup läuft. Das spart Anfragen und gibt Nutzern sofort Rückmeldung. Für E-Mail-Adressen ist der Domainteil genauso zu behandeln, die Feinheiten beim lokalen Teil stehen im Beitrag zur E-Mail-Validierung in PHP.

Für eine Prüfung im Browser kannst du das Muster in JavaScript wiederverwenden. Nur idn_to_ascii() gibt es dort nicht, den Ersatz liefert new URL('http://' + eingabe).hostname, das ebenfalls Punycode erzeugt.

Hast du einen gültigen Namen und willst wissen, ob er frei ist? Gib ihn in die betadomain-Suche ein. Für Entwicklerprojekte lohnt ein Blick auf die .tech-Endung für 67,99 € im Jahr, und die Domain-API von betadomain prüft dieselbe Frage per Code.

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

Häufige Fragen

Reicht filter_var() mit FILTER_VALIDATE_DOMAIN?

Für eine Formprüfung von Hostnamen ja, für Domains im Sinne von „registrierbar im Internet“ nicht. Mit FILTER_FLAG_HOSTNAME akzeptiert filter_var() auch localhost, 1.2.3.4 und a.b. Umlaut-Domains lehnt es ab, solange du sie nicht vorher in Punycode umwandelst.

Sind Unterstriche in Domainnamen erlaubt?

In Hostnamen nicht, im DNS schon. Records wie _dmarc.example.com oder _sip._tcp.example.com nutzen Unterstriche bewusst. Für Domains, die jemand registrieren oder im Browser aufrufen soll, gehört der Unterstrich nicht ins Muster.

Woher weiß ich, ob die Endung existiert?

Gar nicht per Regex. Das Muster prüft nur die Form. Ob .beispiel eine echte Endung ist, verrät die IANA-Liste der Top-Level-Domains oder die Public Suffix List. Für Nutzereingaben reicht oft ein Abgleich mit der Liste der Endungen, die du tatsächlich anbietest.

Darf ein Domainname mit einer Ziffer beginnen?

Ja. RFC 1123 hat die alte Regel aus RFC 1035 gelockert, dass Labels mit einem Buchstaben beginnen müssen. Domains wie 1und1.de oder 123.com sind gültig. Nur die Endung selbst besteht in der Praxis nicht aus reinen Ziffern.