Why Domain Name Validation Matters
Whenever you accept a URL, an email address or an origin allowlist entry from a user, you end up validating a domain name. Sloppy validation lets junk into your database:www..example.com, -example-.com, example with no TLD — all of which should be rejected before hitting a downstream DNS lookup.
A good regex catches the obvious wins: right character set, sensible length, no leading or trailing hyphens on any label, at least one dot before a plausible TLD. It is not a replacement for actually resolving the domain (only DNS can tell you whether example-that-does-not-exist.com is real) but it stops garbage input at the form.
Domain Name Rules in Plain English
Per RFC 1035 and RFC 3696, a valid domain name is:
- One or more labels separated by dots (
example.comhas 2 labels). - Each label is 1–63 characters long.
- Each label contains only ASCII letters (
a-z,A-Z), digits (0-9) or hyphens (-). - A label cannot start or end with a hyphen.
- The full domain (all labels + dots) cannot exceed 253 characters.
- The rightmost label (the TLD) must be at least 2 letters.
Internationalised domain names (IDNs) like café.com are handled by punycode: the domain is converted to xn--caf-dma.com which is pure ASCII and passes the same regex. Do the conversion first, then match.
Breaking Down the Pattern
/^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z]{2,})+$/^— Start anchor.(?!-)— Negative lookahead: the label must not start with a hyphen.[A-Za-z0-9-]{1,63}— First label: 1–63 characters from the allowed set.(?<!-)— Negative lookbehind: the label must not end with a hyphen.(\.[A-Za-z]{2,})+— One or more dot-plus-TLD groups, letting you match bothexample.comandsub.example.co.uk.$— End anchor; nothing may follow the TLD.
Language-Specific Usage
JavaScript
const DOMAIN_RE = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z]{2,})+$/;
function isValidDomain(s) {
if (s.length > 253) return false;
return DOMAIN_RE.test(s);
}
isValidDomain('example.com'); // true
isValidDomain('sub.example.co.uk'); // true
isValidDomain('my-site.io'); // true
isValidDomain('-invalid.com'); // false
isValidDomain('example'); // false (no TLD)
Python
import re
DOMAIN_RE = re.compile(r'^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z]{2,})+$')
def is_valid_domain(s):
if len(s) > 253:
return False
return bool(DOMAIN_RE.match(s))
print(is_valid_domain('example.com')) # True
print(is_valid_domain('sub.example.co.uk')) # True
print(is_valid_domain('example')) # False
PHP
<?php
function isValidDomain($s) {
if (strlen($s) > 253) return false;
return (bool) preg_match('/^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z]{2,})+$/', $s);
}
var_dump(isValidDomain('example.com')); // true
var_dump(isValidDomain('-invalid.com')); // false
Extracting the Domain From a URL
If you receive full URLs and only want the domain, capture the host portion between the protocol and the first path/port/query character:
const URL_HOST = /^https?:\/\/([^\/\s:?#]+)/;
function extractDomain(url) {
const match = url.match(URL_HOST);
return match ? match[1] : null;
}
extractDomain('https://api.example.com/v2?token=x'); // 'api.example.com'
extractDomain('http://localhost:3000/foo'); // 'localhost'
extractDomain('not-a-url'); // null
For production code prefer the built-in URL API — it handles IPv6 addresses, port numbers and userinfo correctly:
function extractDomainSafe(url) {
try { return new URL(url).hostname; } catch { return null; }
}
Reject Bare IPs and Localhost If You Need To
The default regex accepts 1.2.3.4 — because 1, 2,3 and 4 all look like valid single-character labels and [A-Za-z]{2,} matches nothing after the last dot... wait, it does not match. 1.2.3.4 fails because the last group requires 2+ letters. Good.
But example.examplepasses even though it's not a real domain — that's expected: regex cannot know which TLDs exist. If you must reject non-real TLDs, load the IANA TLD list and check the last segment against it.
Explore Related Regex Patterns
- Regex for URL matching — full URL parsing.
- Regex for email validation — includes the domain part.
- Regex for IP addresses — IPv4 and IPv6.
- Regex for hex colours — #RGB and #RRGGBB.
- Regex for 24-hour time — HH:MM validation.
- Regex for credit card numbers — Visa, Mastercard, Amex.
Frequently Asked Questions
Should I strip the leading www. before validating?
Not strictly needed — www.example.com passes the regex just fine as a subdomain. If you want to normalise all forms to the bare apex domain, strip www. after validation.
Are single-label domains like "localhost" valid?
The regex requires at least one dot, so localhostdoes not match. That's usually what you want for public URLs. For internal tools that accept short hostnames, adjust the pattern by making the (\\.TLD)+ group optional.
Can I match punycode (xn--) domains?
Yes — xn--caf-dma.com passes because xn--caf-dma is a valid label under the ASCII rules. Always canonicalise incoming domains to punycode before storage and comparison so the same domain never appears twice in different forms.
What's the max length again — 253 or 255 characters?
253 characters is the maximum for the domain-name representation (the dotted string). 255 is the octet limit in DNS wire format including the length prefixes. For validation use 253.