Why test.example.com breaks WHOIS — and the fix
22 May 2026 · 4 min read
The bug report
Our admin service-detail page shows WHOIS data for any cPanel domain — registrar, abuse contact, expiry, nameservers — pulled live via RDAP (the modern JSON replacement for WHOIS, served by IANA-coordinated registry servers).
A few days after we shipped that surface, a customer asked why their service for test.gocelerus.com showed "WHOIS temporarily unavailable". The card kept retrying. The cache kept refusing to populate.
Their service was perfectly fine. The lookup wasn't.
Why RDAP doesn't answer on subdomains
RDAP servers hold one record per registered domain. gocelerus.com is registered with a registrar (Tucows in our case); it has an entry in the .com RDAP server. test.gocelerus.com is not a registered domain — it's a host record under our own DNS, set up via cPanel.
When you query the .com RDAP endpoint for test.gocelerus.com, you get:
{ "errorCode": 404, "title": "Object not found" }
Some implementations return "temporarily unavailable" instead of 404 to be friendlier to misconfigured clients. Either way, you're not getting an answer because the answer doesn't exist at that level.
The fix is to ask the right question: query gocelerus.com, not test.gocelerus.com. That's the registered name. And that's where the trouble starts.
"Just strip the subdomain" — except you can't
The naive approach is to keep the last two labels and drop everything before:
test.gocelerus.com → gocelerus.com ✓
www.test.example.com → example.com ✓
deep.www.example.com → example.com ✓
That works for .com, .net, .org. It breaks on country-code TLDs that use a second level:
shop.tokopedia.co.id → co.id ✗ (not a domain, it's a suffix)
api.gocelerus.com.au → com.au ✗
mail.example.co.uk → co.uk ✗
co.id and co.uk and com.au are public suffixes — multi-label TLDs operated by the country registry. Anyone can register under them (tokopedia.co.id), but co.id itself isn't a registered domain.
The Public Suffix List (PSL) maintained by Mozilla is the canonical answer here. It's about 10,000 lines long and updates regularly. We didn't want to ship that as a dependency for a feature that touches maybe 20 distinct TLDs in our customer base.
A small curated suffix table
We compromised. The default is the last two labels. For the country-code TLDs our customers actually use, we keep a small in-memory list of three-label suffixes that need an extra peel:
private array $multiLabelSuffixes = [
'co.id', 'com.au', 'co.uk', 'com.sg', 'com.kh',
'com.my', 'co.jp', 'co.kr', 'co.in', 'com.cn',
'co.nz', 'com.br',
// ...
];
The reducer logic is one careful loop:
public function registrableDomain(string $host): string
{
$parts = explode('.', strtolower($host));
if (count($parts) < 2) return $host;
$tail2 = implode('.', array_slice($parts, -2));
if (in_array($tail2, $this->multiLabelSuffixes, true)
&& count($parts) >= 3) {
return implode('.', array_slice($parts, -3));
}
return $tail2;
}
test.gocelerus.com → gocelerus.com. api.tokopedia.co.id → tokopedia.co.id. deep.subdomain.example.com.my → example.com.my. The RDAP lookup hits the right registry, gets a real answer, and the UI populates.
What we didn't do
- Pull in the full PSL. Overkill for our customer base. If we ever hit a TLD our list doesn't cover, adding it is one line. The trade-off is visibility — we know exactly which suffixes the system handles.
- Auto-fetch the PSL at runtime. That's a network dependency for what should be a static lookup. The PSL doesn't change that fast.
- Make RDAP lookups optional. We considered hiding the WHOIS card on subdomain services. But customers who own their domain still want to see the registrar info — they just want it pulled from the right level.
What we did add: diagnostic logging
Future "WHOIS unavailable" reports shouldn't require a debugger. The RDAP client now logs four distinct event types:
rdap.lookup.no_tld— host parsing returned nothing usefulrdap.lookup.no_server— IANA bootstrap had no RDAP server for that TLDrdap.lookup.exception— network or parse errorrdap.lookup.non_2xx— RDAP server returned a non-success status (with HTTP code + first 300 chars of body)
A grep through Railway logs now tells me which class of failure happened, for which host, on which TLD. Triage went from "open a debugger" to "check the log".
The takeaway
Edge cases in DNS-adjacent systems almost always come back to "you're querying the wrong object". RDAP isn't broken on subdomains — it's correctly telling you the subdomain isn't a registered name. The fix is upstream: ask the right question, with a small reducer that knows where the registered name lives.
20 lines of code, 12 entries in a suffix table, four named log events. Customer's WHOIS card now populates within a second.