Quick answer
An IPv6 regex has to list every place the :: compression can appear, so it is long: one alternative for eight full hextets and seven more for :: at the start, middle or end. The complete pattern matches 2001:db8::1, ::1, fe80:: and the full 2001:0db8:85a3:0000:0000:8a2e:0370:7334, and rejects two :: in one address, groups over four hex digits and nine-group addresses.
- Tested examples
- Token-by-token breakdown
- Code in 5 languages
- Live tester
How to use the IPv6 address regex
- 1Copy the pattern with Copy pattern in the tester, or take a ready-made snippet for your language from the code section below.
- 2Check the sample lines in Test string: every line the IPv6 address regex accepts is highlighted, rejected lines stay plain.
- 3Paste your own values one per line to see which pass, and read Explanation for what each part of the pattern does.
- 4Open the Code tab for JavaScript, Python, Go, C#, PHP or Dart with the pattern translated and escaped.
- 5Validate again on the server: client-side checks give users quick feedback, not security.
How does the IPv6 address regex work?
Read it left to right. Each piece below matches one part of the input:
(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}Eight full hextets: 1:2:3:4:5:6:7:8.(?:[0-9A-Fa-f]{1,4}:){1,7}:One to seven hextets then :: at the end, like fe80::.(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}:: before the last hextet, like 2001:db8::1.(?:…){1,5}(?::…){1,2} … (?:…){1,2}(?::…){1,5}The :: in every middle position, with at most seven hextets in total.[0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}One hextet, then :: and up to six more.:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:):: at the very start, like ::1, or the unspecified address :: itself.
Which strings does the IPv6 address regex match?
The whole input must match, from the first character to the last. Every row below is checked against the pattern by our automated test suite before each release.
| Input | Matches? | Why |
|---|---|---|
2001:0db8:85a3:0000:0000:8a2e:0370:7334 | Yes | Full form, eight hextets. |
2001:db8:85a3::8a2e:370:7334 | Yes | Zeros compressed in the middle, leading zeros dropped. |
2001:db8::1 | Yes | The documentation prefix with a host part. |
::1 | Yes | Loopback. |
:: | Yes | The unspecified address. |
fe80:: | Yes | Link-local prefix, compressed at the end. |
FE80::1FF:FE23:4567:890A | Yes | Uppercase hex is allowed. |
2001:db8::1::1 | No | :: may appear only once. |
12345::1 | No | A hextet has at most four hex digits. |
2001:db8:85a3:0:0:8a2e:370:7334:1 | No | Nine hextets. |
gggg::1 | No | g is not a hex digit. |
192.168.1.1 | No | An IPv4 address. |
:1 | No | A single leading colon is not valid. |
Which IPv6 forms does this regex leave out?
Two legal forms are deliberately not covered, to keep the pattern readable:
- Embedded IPv4, such as
::ffff:192.0.2.1(IPv4-mapped) and64:ff9b::192.0.2.1(NAT64). These end in a dotted quad. - Zone IDs, such as
fe80::1%eth0, which name the network interface for link-local addresses.
If your input can contain them, use a parser instead of growing the regex further: net.isIPv6() in Node.js, ipaddress.IPv6Address in Python, netip.ParseAddr in Go, IPAddress.TryParse in .NET or filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) in PHP. They also handle the address types (RFC 4291) for you.
How should I store and compare IPv6 addresses?
The same address has many spellings: 2001:DB8:0:0:0:0:0:1, 2001:db8::1 and 2001:0db8::0001 are identical. Comparing strings will report them as different. Normalize to the canonical form from RFC 5952 before storing or comparing: lowercase, no leading zeros, and :: replacing the longest run of zero groups. Python's str(ipaddress.ip_address(s)) and Go's netip.Addr.String() both produce it.
How do I use the IPv6 address regex in JavaScript, Python, Go, C# and PHP?
The pattern is translated for each engine rather than pasted as is: named groups, anchors, escapes, shorthand classes and flags are adjusted so each snippet behaves as closely to the JavaScript version as its engine allows. Differences that remain (Go's RE2 has no lookarounds or backreferences, for example) are called out under each one. The Code tab in the tester above also exports Dart, and updates as you edit the pattern.
JavaScript and TypeScript
const regex = /^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:))$/;
function isValid(value) {
return regex.test(value);
}
console.log(isValid("2001:0db8:85a3:0000:0000:8a2e:0370:7334"));Python
import re
pattern = re.compile(r"^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:))\Z")
def is_valid(value: str) -> bool:
return pattern.search(value) is not None
print(is_valid("2001:0db8:85a3:0000:0000:8a2e:0370:7334"))- JavaScript's $ only matches at the very end of the string. Python's $ also matches before a trailing newline, so the pattern uses \Z instead.
Go
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:))$`)
fmt.Println(re.MatchString("2001:0db8:85a3:0000:0000:8a2e:0370:7334"))
}C#
using System;
using System.Text.RegularExpressions;
// The timeout stops a pathological input from hanging the thread (ReDoS).
var regex = new Regex(@"^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:))\z", RegexOptions.None, TimeSpan.FromSeconds(1));
Console.WriteLine(regex.IsMatch("2001:0db8:85a3:0000:0000:8a2e:0370:7334"));- JavaScript's $ only matches at the very end of the string. .NET's $ also matches before a trailing newline, so the pattern uses \z instead.
PHP
<?php
$pattern = '/^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|:(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:))$/D';
function isValid(string $value, string $pattern): bool
{
return preg_match($pattern, $value) === 1;
}
var_dump(isValid('2001:0db8:85a3:0000:0000:8a2e:0370:7334', $pattern));- The D modifier makes $ match only at the very end, like JavaScript (PCRE's $ also matches before a trailing newline).
Which related regex patterns are useful?
- Regex for IPv4 Addresses: Four octets from 0 to 255, no leading zeros, nothing before or after.
- Regex for MAC Addresses: Six hex pairs with consistent : or - separators, or Cisco's dotted form.
- Regex for Domain Names: Hostnames with valid labels, a 253-character limit and Punycode support.
- Regex for URL Validation: http and https links with a real domain, optional port, path, query and fragment.
Browse all 25 patterns in the regex pattern library, or build your own in the regex tester.
Frequently asked questions
Last updated by the Playxoft engineering team.