A whois lookup in PHP is a TCP connection to port 43: open it with fsockopen(), send the domain followed by \r\n and read until the server closes the socket. whois.iana.org tells you which server handles each TLD. Responses are free text, so detecting a free domain means matching registry-specific phrases such as "No match for".
There’s something refreshing about whois. No TLS, no JSON, no auth: you open a socket, type a name, get text back. RFC 3912 covers the whole protocol in about three pages. The pain sits in the text you get back, which every registry formats its own way.
How a whois lookup works on the wire
The client connects to TCP port 43, sends one line terminated by \r\n and reads until the server closes the connection. That’s it. Try it from a shell before writing PHP, so you know what the response should look like:
whois -h whois.verisign-grs.com example.com
In PHP the same thing is fsockopen(), fwrite() and a read loop. Two timeouts matter: one for connecting (the fifth argument of fsockopen()) and one for reading (stream_set_timeout()). Skip the second and a server that keeps the socket open will hang your PHP-FPM worker until max_execution_time kills it.
<?php
declare(strict_types=1);
function whoisQuery(string $server, string $query, int $timeout = 10): string
{
$fp = @fsockopen($server, 43, $errno, $errstr, $timeout);
if ($fp === false) {
throw new RuntimeException("Cannot connect to $server: $errstr ($errno)");
}
stream_set_timeout($fp, $timeout);
fwrite($fp, $query . "\r\n");
$response = '';
while (feof($fp) === false) {
$chunk = fread($fp, 8192);
if ($chunk === false || stream_get_meta_data($fp)['timed_out']) {
break;
}
$response .= $chunk;
}
fclose($fp);
return $response;
}
Finding the right whois server for each TLD
Hard-coding servers works until a registry migrates. IANA runs whois.iana.org, which answers a TLD query with a record containing a whois: line.
<?php
declare(strict_types=1);
function whoisServerFor(string $tld): ?string
{
static $cache = [];
if (array_key_exists($tld, $cache)) {
return $cache[$tld];
}
$answer = whoisQuery('whois.iana.org', $tld);
return $cache[$tld] = preg_match('/^whois:[ \t]+(\S+)/mi', $answer, $m) ? $m[1] : null;
}
Note [ \t]+ rather than \s+. For .dev the whois: line is empty, and \s+ happily crosses the newline and captures the next token. My first version reported status: as the whois server for .dev. With the fix the function returns null, and you know to use RDAP in PHP instead.
| TLD | Whois server (via IANA) | Reply for an unregistered name |
|---|---|---|
| .com / .net | whois.verisign-grs.com | No match for domain "NAME.COM". |
| .io | whois.nic.io | Domain not found. |
| .eu | whois.eu | Status: AVAILABLE |
| .dev | (none) | RDAP only |
Thin registries and registrar referrals
Verisign runs .com and .net as thin registries. The registry response contains dates, nameservers and the sponsoring registrar, plus a Registrar WHOIS Server: line. Anything more lives on the registrar’s server. The wrapper below follows that referral once and appends the result:
<?php
declare(strict_types=1);
function whois(string $domain): string
{
$ascii = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
if ($ascii === false) {
throw new InvalidArgumentException("Invalid domain name: $domain");
}
$tld = substr(strrchr($ascii, '.'), 1);
$server = whoisServerFor($tld) ?? throw new RuntimeException("No whois server for .$tld");
// whois.denic.de (.de) expects its own syntax
$query = $server === 'whois.denic.de' ? "-T dn,ace $ascii" : $ascii;
$raw = whoisQuery($server, $query);
// thin registry: follow the registrar referral
if (preg_match('/Registrar WHOIS Server:\s*(\S+)/i', $raw, $m) && strcasecmp($m[1], $server) !== 0) {
try {
$raw .= "\n" . whoisQuery($m[1], $ascii);
} catch (RuntimeException) {
// registrar server down: registry data is usually enough
}
}
return $raw;
}
Always send punycode. Some servers return “not found” for bücher.com even when xn--bcher-kva.com exists. The punycode guide covers conversion and the flags that matter.
Detecting an available domain in whois output
There’s no status code, so you match phrases. This list covers the registries above; you’ll extend it for yours.
<?php
declare(strict_types=1);
function looksFree(string $raw): bool
{
$patterns = [
'/^Status:\s*free/mi', // DENIC (.de)
'/^Status:\s*AVAILABLE/mi', // EURid (.eu)
'/^No match for/mi', // Verisign (.com, .net)
'/^Domain not found/mi', // e.g. .io
'/^NOT FOUND/mi',
'/^The queried object does not exist/mi',
];
foreach ($patterns as $p) {
if (preg_match($p, $raw)) {
return true;
}
}
return false;
}
foreach (['php.net', 'gibt-es-sicher-nicht-4711.com', 'nic.io', 'gibt-es-sicher-nicht-4711.io', 'europa.eu', 'gibt-es-sicher-nicht-4711.eu'] as $d) {
$raw = whois($d);
echo str_pad($d, 32), looksFree($raw) ? 'free' : 'taken', ' (', strlen($raw), " bytes)\n";
}
Real output from my machine:
php.net taken (6476 bytes)
gibt-es-sicher-nicht-4711.com free (2290 bytes)
nic.io taken (9853 bytes)
gibt-es-sicher-nicht-4711.io free (2759 bytes)
europa.eu taken (4224 bytes)
gibt-es-sicher-nicht-4711.eu free (2163 bytes)
The logic only works one way. A match means free; no match means you didn’t recognise the answer. That includes rate-limit messages and empty replies, which is why an availability tool needs an authoritative source as described in checking domain availability with PHP.
Pulling fields out of the text
Usually you want the expiry date, the nameservers and the registrar. Field names differ: Verisign writes Registry Expiry Date:, many registrar servers Registrar Registration Expiration Date:. Nameservers appear as Name Server: in upper case. A pattern like /^\s*Name Server:\s*(\S+)/mi with preg_match_all(), followed by strtolower(), covers most gTLDs. Parse dates with DateTimeImmutable inside a try/catch, since formats range from ISO 8601 to 17-Nov-2029.
If what you really want is to notice nameserver changes, DNS is the better tool. The DNS monitoring guide shows a cron-friendly script.
Whois or RDAP: which one to call first?
For gTLDs the answer has shifted. ICANN required RDAP from gTLD registries years ago, and since 28 January 2025 registries and registrars under ICANN contract are no longer obliged to operate port 43 whois at all. Some still do, others have frozen or shut their servers. If your list of extensions is mostly .com, .org, .app and the newer gTLDs, start with RDAP and treat whois as a fallback.
Country-code TLDs are the opposite case. They aren’t bound by ICANN policy, and quite a few don’t appear in the IANA RDAP bootstrap. In my test, .io, .co and .eu were all missing from it while their whois servers answered fine. For those, whois is still the primary route.
A simple strategy that holds up:
- Look up the TLD in the RDAP bootstrap.
- If there’s an entry, query RDAP and stop.
- Otherwise ask whois.iana.org for a whois server and query that.
- If neither exists, return “unknown” and log the TLD so you can add a manual mapping.
The extra step costs one cached lookup per TLD and saves you from maintaining two complete code paths for every extension.
Limits, encodings and privacy
Registries throttle without warning. Too many queries and you’ll see “Query limit exceeded”, an empty response or a refused connection. During testing for this guide, one ccTLD server started returning zero bytes after a few dozen lookups. Treat empty replies as “unknown”, never as “free”.
Encoding isn’t specified either: some servers send UTF-8, some Latin-1. Check with mb_check_encoding() before storing anything. And don’t expect contact data. Since GDPR, registrant details are redacted almost everywhere, which is fine for technical use and a dead end for lead scraping.
If you’d rather receive the registry fields as JSON, betadomain’s whois_lookup MCP tool does the server discovery for you; see the betadomain developer page. And when whois says your name is free, search it on betadomain and check the .com renewal price before you register.
Step by step
- Find the whois serverQuery whois.iana.org with the TLD and read the whois: line. Cache the result.
- Prepare the queryConvert the domain to punycode. Some servers need a special syntax; most just take the bare name.
- Connect on port 43Open the socket with fsockopen(), set a read timeout, send the query with \r\n and read until feof().
- Parse the replyMatch registry-specific phrases for free domains and follow Registrar WHOIS Server referrals for thin registries.
Found your domain? You can check and register it directly at domain registration without teaser pricing – yearly price from €2.99.
FAQ
Is there a Composer package for whois?
Yes, io-developer/php-whois on Packagist knows the servers and response formats of many TLDs and also speaks RDAP. For a handful of extensions your own fsockopen() code is fine; for hundreds the package saves a lot of maintenance.
Why does a .dev whois lookup fail?
Some registries no longer run port 43 at all. whois.iana.org returns an empty whois field for .dev, so the only public interface is RDAP. Your code should fall back to RDAP rather than throw.
How many whois queries can I send?
Each registry sets its own limit and very few publish it. Once you exceed it you may get an error line, an empty reply or a refused connection. Cache results and spread bulk lookups over time.
Can I get the registrant's email address via whois?
Almost never since GDPR came into force in 2018. Registries and registrars redact personal data or replace it with a web form or relay address. Whois is still useful for technical data: expiry date, nameservers and registrar.