URL validation regex in JavaScript can check basic structure, but it is the wrong primary tool for the job: the native URL constructor parses URLs correctly, and a catastrophic-backtracking regex on untrusted input is itself a denial-of-service risk. If you need URL validation in JavaScript, reach for new URL() with an explicit protocol allowlist first, and treat regex as a supplementary format check on top, never as your security boundary. This post shows a regex for URL validation that is reasonable, explains where each approach breaks, and lays out the pattern that actually holds up in production.
Why People Reach for a Regex (and Why It Bites)
The request seems simple: "is this string a valid URL?" So someone pastes a regex from Stack Overflow. The trouble is that the URL grammar (RFC 3986, plus WHATWG's living standard for what browsers actually accept) is genuinely complicated: schemes, userinfo, internationalized hosts, ports, percent-encoding, IPv6 literals in brackets, query and fragment rules. A regex that covers all of it is unreadable; a regex that covers "most of it" both rejects valid URLs and accepts invalid ones.
Worse, ambitious URL regexes are a classic source of ReDoS (regular-expression denial of service, CWE-1333). Nested quantifiers over alternations, run against a crafted input, can take exponential time and freeze your event loop. On a Node server that is a single-request outage of the whole process. So a URL validation regex is not just imprecise; on untrusted input it can be a vulnerability.
A Reasonable URL Validation Regex (With Its Limits Stated)
If you want a lightweight format check (for a UI hint, not a security gate), here is a readable regex for URL validation in JavaScript that avoids catastrophic backtracking:
const urlPattern =
/^https?:\/\/[^\s/$.?#].[^\s]*$/i;
function looksLikeUrl(value) {
return urlPattern.test(value);
}
What it does: requires http:// or https://, then at least one non-whitespace host character, then anything that is not whitespace. It uses no nested quantifiers, so it will not ReDoS. What it does not do: verify the host is well-formed, reject http://evil.com@good.com confusion, validate ports, or handle IPv6. It is a smell test, nothing more. Ship it as a form hint if you like, but do not make a trust decision on it.
Every "comprehensive" URL regex you find will be some trade of readability, correctness, and backtracking safety. There is no clean win, which is the argument for not fighting this battle in regex at all.
The Better Tool: The URL Constructor
The WHATWG URL constructor is built into every modern browser and Node. It parses to the same standard browsers use, and it throws on inputs it cannot parse:
function parseUrl(value) {
try {
return new URL(value);
} catch {
return null; // not a parseable URL
}
}
This is more correct than any regex because it is the reference implementation, and it gives you structured fields (protocol, hostname, port, pathname) to make decisions on rather than a boolean. The one gotcha: new URL() accepts more than you probably want. new URL('javascript:alert(1)') parses fine. new URL('file:///etc/passwd') parses fine. Parseable is not the same as safe.
The Pattern That Actually Holds: Parse, Then Allowlist
Security comes from what you do after parsing. Validate the components against an allowlist:
function validateHttpUrl(value, allowedHosts) {
let url;
try {
url = new URL(value);
} catch {
return { ok: false, reason: "unparseable" };
}
// 1. Protocol allowlist -- reject javascript:, data:, file:, etc.
if (url.protocol !== "https:" && url.protocol !== "http:") {
return { ok: false, reason: "protocol" };
}
// 2. Host allowlist, if you have one
if (allowedHosts && !allowedHosts.includes(url.hostname)) {
return { ok: false, reason: "host" };
}
return { ok: true, url };
}
The protocol check is the security-critical line. Accepting a javascript: URL where you expected a link is how you get DOM-based XSS; accepting file: or gopher: where you fetch server-side is how you get SSRF and local file disclosure. Regex almost never gets this right because people validate the shape and forget the scheme.
The SSRF Angle: Validation Is Not Enough for Server-Side Fetches
If your server takes a user-supplied URL and fetches it (webhooks, link previews, "import from URL"), format validation does not protect you. An attacker supplies a perfectly well-formed URL pointing at http://169.254.169.254/ (cloud metadata) or http://localhost:6379/ (internal Redis), and a naive server happily fetches it. This is server-side request forgery, CWE-918.
Defending it needs more than validation:
- Resolve the hostname and check the resolved IP against blocklists for private, loopback, link-local, and metadata ranges, then pin that IP for the actual request (resolve-and-pin), because DNS can rebind between your check and your fetch.
- Disable or constrain redirects, since a permitted host can 302 you to an internal one.
- Prefer an allowlist of destination hosts over a blocklist of bad ones whenever the use case allows.
No regex, however clever, addresses any of this. This is why "validate the URL" and "safely fetch a user URL" are different problems, and conflating them is a common source of findings that a DAST scan picks up in staging.
Where This Goes Wrong in Dependencies
A large share of URL-handling bugs live in libraries, not your code: URL parsers, validators, and normalizers with ReDoS or parsing-differential flaws show up in advisories regularly. If you pull in a validation package, its regex is now your regex, and its ReDoS is now your outage. Keeping those dependencies patched via an SCA tool matters as much as writing your own checks carefully, because a validator that silently disagrees with your fetch client's parser is exactly how a bypass slips through.
FAQ
What is the best regex for URL validation in JavaScript?
There isn't a good one for security purposes. A simple anchored pattern like ^https?:\/\/[^\s/$.?#].[^\s]*$ works as a lightweight format hint without ReDoS risk, but for any trust decision use the native URL constructor and validate the parsed protocol and host instead.
Why is URL validation regex risky?
Comprehensive URL regexes tend to use nested quantifiers that can trigger catastrophic backtracking (ReDoS, CWE-1333), freezing a Node event loop on crafted input. They also commonly validate structure while ignoring the scheme, letting dangerous javascript: or file: URLs through.
How do I validate a URL in JavaScript safely?
Parse with new URL(value) inside a try/catch, then allowlist the protocol (accept only http:/https:) and, where possible, the hostname. The parser handles correctness; your allowlist handles safety. Regex, if used at all, is only a supplementary format hint.
Does validating a URL prevent SSRF?
No. Format validation does not stop a well-formed URL from pointing at internal addresses like cloud metadata endpoints. Preventing SSRF requires resolving the host, checking the resolved IP against private and link-local ranges, pinning that IP, and constraining redirects, ideally with a destination allowlist.