How to Check if a Domain Is Available (3 Methods, 2026)
March 26, 2026
You feed 200 domain ideas into your checker. Half come back as "available." You try to register the first one and it is taken. Your checker said it was free because the domain had no nameservers configured, and your code only checked DNS.
There are three ways to check domain availability: DNS resolution, WHOIS lookup, and RDAP query. Each gives you a different tradeoff between speed, accuracy, and reliability.
Quick summary
| Method | Speed | Accuracy | Rate limits | TLD coverage |
|---|---|---|---|---|
| DNS | Fast (~50ms) | Unreliable | None | All |
| WHOIS | Slow (~2s) | Good (deprecated) | Aggressive | Most |
| RDAP | Medium (~500ms) | Authoritative | Moderate | 1,200+ TLDs |
Important caveat first: "available" is not the same as "registrable"
Before diving into code, understand what these methods actually tell you. A domain returning 404 from RDAP (or "not found" from WHOIS) means it is not in the registry database. That does not always mean you can register it:
- Reserved domains — some names are reserved by the registry (single-character domains, country names, etc.)
- Premium domains — available but at a higher price set by the registry
- Pending delete — expired and being purged, not yet available for new registration
- Trademark blocks — domains blocked under ICANN's Trademark Clearinghouse
RDAP and WHOIS tell you whether a domain is currently registered. To know if you can actually buy it, you need to check with an ICANN-accredited registrar.
Method 1: DNS resolution
The fastest approach. If a domain has DNS records, it is definitely taken. If it does not, it might be available, but you cannot be sure.
import dns.resolver # pip install dnspython
def is_registered_dns(domain):
try:
dns.resolver.resolve(domain, "NS")
return True
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
return False
except dns.resolver.NoNameservers:
return False # Or True — ambiguous
import { Resolver } from "dns/promises";
const resolver = new Resolver();
async function isRegisteredDns(domain) {
try {
await resolver.resolveNs(domain);
return true;
} catch (err) {
if (err.code === "ENOTFOUND" || err.code === "ENODATA") {
return false;
}
return false; // Ambiguous
}
}
Pros: Fast, free, no rate limits, works for every TLD.
Cons: Unreliable. A registered domain without nameservers returns the same result as an unregistered domain. This happens more than you would expect:
- Newly registered domains before DNS propagation
- Parked domains with no DNS configuration
- Domains in redemption period (expired but not yet deleted)
- Domains with lapsed nameservers
DNS can tell you a domain is definitely registered (it has NS records), but it cannot tell you a domain is definitely available. The false negative rate is significant.
Method 2: WHOIS lookup
The traditional approach. Query the WHOIS server and parse the text response.
import whois # pip install python-whois
def is_registered_whois(domain):
try:
w = whois.whois(domain)
return w.domain_name is not None
except whois.exceptions.WhoisDomainNotFoundError:
return False # Domain not in registry
except Exception:
return None # Connection error, rate limited, or unexpected TLD behavior
// npm install whois-json
// Note: whois-json shells out to the system `whois` command.
// You need whois installed (apt install whois / brew install whois).
import whois from "whois-json";
async function isRegisteredWhois(domain) {
try {
const result = await whois(domain);
// "No match" patterns vary by registry — the library handles most of them
if (!result.domainName) return false;
return true;
} catch {
return null; // Error or rate limited
}
}
Pros: More accurate than DNS. A WHOIS response means the domain is registered in the registry database, regardless of DNS configuration.
Cons:
- Deprecated. ICANN sunsetted WHOIS for gTLDs in January 2025.
- Aggressive rate limits. Many registries block your IP after a few queries. See WHOIS rate limits.
- No standard "not found" response. Every registry returns different text for unregistered domains. Libraries try to handle this, but the list of patterns is never complete.
- Some TLDs severely restrict WHOIS.
.de(Germany) only allows individual lookups through their web interface and limits port 43 access.
WHOIS still works today for many TLDs, but it is the wrong foundation for new code.
Method 3: RDAP query
RDAP returns HTTP status codes. A registered domain returns 200. An unregistered domain returns 404. No text parsing needed.
import requests
# IANA bootstrap — maps TLDs to RDAP servers
# Cache this. It changes rarely and IANA does not appreciate being hammered.
BOOTSTRAP_URL = "https://data.iana.org/rdap/dns.json"
_bootstrap_cache = None
def get_rdap_server(tld):
global _bootstrap_cache
if _bootstrap_cache is None:
_bootstrap_cache = requests.get(BOOTSTRAP_URL, timeout=10).json()
for tlds, urls in _bootstrap_cache["services"]:
if tld in tlds:
return urls[0]
return None # TLD not in bootstrap (some ccTLDs)
def is_registered_rdap(domain):
tld = domain.rsplit(".", 1)[-1]
server = get_rdap_server(tld)
if not server:
return None # TLD has no RDAP server
resp = requests.get(f"{server}domain/{domain}", timeout=10)
if resp.status_code == 200:
return True # Registered
if resp.status_code == 404:
return False # Not found in registry
return None # Error or rate limited
const BOOTSTRAP_URL = "https://data.iana.org/rdap/dns.json";
let bootstrapCache = null;
async function getRdapServer(tld) {
if (!bootstrapCache) {
const resp = await fetch(BOOTSTRAP_URL);
bootstrapCache = await resp.json();
}
for (const [tlds, urls] of bootstrapCache.services) {
if (tlds.includes(tld)) return urls[0];
}
return null; // TLD not in bootstrap (some ccTLDs)
}
async function isRegisteredRdap(domain) {
const tld = domain.split(".").pop();
const server = await getRdapServer(tld);
if (!server) return null;
const resp = await fetch(`${server}domain/${domain}`);
if (resp.status === 200) return true; // Registered
if (resp.status === 404) return false; // Not found
return null; // Error or rate limited
}
Pros:
- HTTP status codes — 200 means registered, 404 means not found. No text parsing.
- Authoritative — the response comes directly from the registry database.
- Structured data — if registered, you also get expiration dates, registrar, nameservers, and status in JSON. See The RDAP JSON Response Decoded.
- Standardized — same protocol, same JSON format across 1,200+ TLDs (RFC 9083).
Cons:
- Rate limits still apply — RDAP servers enforce per-IP limits, though generally more generous than WHOIS.
- Bootstrap required — you need to fetch and cache the IANA bootstrap file to know which server to query.
- Not all ccTLDs — some country-code TLDs do not have RDAP servers yet. Coverage is growing, but a few smaller ccTLDs are still WHOIS-only or have no public lookup at all.
The hybrid approach
For production use, combine DNS and RDAP:
- DNS first — check for NS records. If found, the domain is definitely registered. Fast and free.
- RDAP second — if DNS returns NXDOMAIN, confirm with RDAP. This catches registered domains without DNS configuration.
This gives you speed (most registered domains have DNS records) and accuracy (RDAP catches the edge cases).
import dns.resolver
import requests
def is_available(domain):
# Fast check: DNS
try:
dns.resolver.resolve(domain, "NS")
return False # Has nameservers = definitely registered
except dns.resolver.NXDOMAIN:
pass # Might be available, confirm with RDAP
except Exception:
pass # DNS error, fall through to RDAP
# Authoritative check: RDAP
tld = domain.rsplit(".", 1)[-1]
server = get_rdap_server(tld) # From bootstrap (see above)
if not server:
return None # Can't determine — no RDAP server for this TLD
resp = requests.get(f"{server}domain/{domain}", timeout=10)
return resp.status_code == 404 # True = available
import { Resolver } from "dns/promises";
const resolver = new Resolver();
async function isAvailable(domain) {
// Fast check: DNS
try {
await resolver.resolveNs(domain);
return false; // Has nameservers = definitely registered
} catch {
// NXDOMAIN or DNS error — fall through to RDAP for authoritative answer
}
// Authoritative check: RDAP
const tld = domain.split(".").pop();
const server = await getRdapServer(tld); // From bootstrap (see above)
if (!server) return null;
const resp = await fetch(`${server}domain/${domain}`);
return resp.status === 404; // true = available
}
At scale: rate limits become the real problem
The hybrid approach works for checking a few domains at a time. If you need to check hundreds or thousands, you will run into RDAP server rate limits. Each registry enforces its own per-IP limits, and most do not publish them. Check too fast and your IP gets blocked with a 429 response, sometimes for hours.
Building the bootstrap discovery, per-server rate limit tracking, and backoff logic is real work. An API handles that infrastructure:
# Check a single domain — returns 404 if not registered
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://rdapapi.io/api/v1/domain/my-startup-idea.com"
# Check multiple domains at once (Pro and Business plans)
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-X POST "https://rdapapi.io/api/v1/domains/bulk" \
-d '{"domains": ["idea1.com", "idea2.io", "idea3.dev"]}'
RDAP API covers 1,200+ TLDs with server-side caching and rate limit management. Plans start at $9/month for 30,000 lookups.
Further reading
- How to Check Domain Availability with RDAP — RDAP-only approach with code in Python, Node.js, PHP, Go, and Java
- WHOIS Rate Limits Are Killing Your Domain Tool — why WHOIS breaks at scale
- The RDAP JSON Response Decoded — what the RDAP response contains
- IANA RDAP Bootstrap Registry — the TLD-to-server mapping file
- RFC 9083 — RDAP JSON response specification
- API documentation — endpoint reference