One IPv6 address can be written a dozen ways, and only one of them is canonical. Paste any form and this tool returns the RFC 5952 form, expands it back out group by group, and — the part no other compressor does — names the rule it applied, so you can see exactly what was wrong with what you had.
One per line, up to 200. Brackets, a port, a %zone and a /prefix are all understood and put back where they belong.
Quick answer: how do you compress an IPv6 address?
Do three things, in this order. Remove the leading zeros from every four-digit group. Replace the longest run of all-zero groups with :: — and if two runs tie, the first one. Write the hex digits in lowercase. One rule catches people out: :: must never stand for a single zero group, so 2001:db8:0:1:1:1:1:1 is correct and 2001:db8::1:1:1:1:1 is not. The tool above does all four and tells you which ones your address needed.
Robert Harrison
OSINT & Network Utility Expert
Robert covers DNS, ports, SSL, subnet and CIDR maths and the address-format converters on TrustMyIP.
This compressor was written out by hand rather than handed to a library, because browsers have no inet_ntop. To check it, the same 120,105 addresses were compressed by this JavaScript and by the C library behind PHP, then compared: 119,729 came out identical and 376 differed — every one of them the deprecated ::a.b.c.d form described in the section on dotted tails, where the RFC and the C library genuinely disagree. That disagreement is reported on the result rather than hidden.
Published 25 September 2026 · last reviewed 25 September 2026 · More from Robert
An IPv6 address is 128 bits. Written out in full it is eight groups of four hexadecimal digits, which is 39 characters and almost impossible to read aloud. So the format allows shortcuts: drop the zeros at the front of a group, and collapse a run of all-zero groups into a single ::. The trouble is that the shortcuts can be applied in more than one way, and for years everybody applied them differently.
So when people ask how to compress an IPv6 address, the honest answer is that there was no single right way until 2010. That is what RFC 5952 fixed. It was published in August 2010 as a Standards Track document, it updates the older addressing architecture in RFC 4291, and as of September 2026 nothing has obsoleted or replaced it. It does not change the address format at all — every form that was legal before is still legal to read. What it does is pick exactly one of those forms as the canonical one, so that two programs writing down the same address produce the same characters.
This matters more than it sounds. Address text gets compared as text constantly: in an allowlist, in a log search, in a database index, in a firewall rule. 2001:DB8::1 and 2001:db8:0:0:0:0:0:1 are the same address and different strings, so a string comparison says they are different hosts. Canonicalise both first and the problem disappears. If you need to see every group written out instead, our IPv6 expansion tool does the opposite job, and the two round-trip: compress then expand, or expand then compress, and you land back where you started. For the underlying format, our guide to what an IPv6 address is starts from the beginning, and the IPv4 versus IPv6 header comparison shows what those 128 bits actually sit inside.
Nothing you paste here is sent anywhere. The whole compressor is JavaScript running in your own browser — there is no request, no log, and no server to keep anything. That is why it also works offline once the page has loaded, and why it can handle a list of internal addresses you would not want to put into someone else's form.
Each address gets six things. The canonical form is the answer — the one form RFC 5952 says to write. The expanded form is all eight groups padded to four digits, which is what you want when you are lining addresses up in a column or reading a prefix. The rules applied is the part that is missing from every other compressor: it names the section of the RFC that each change came from.
Then there is the address type, matched against the IANA IPv6 Special-Purpose Address Registry — so a 2001:db8:: address is labelled documentation rather than looking like a real host, and an fe80:: one is labelled link-local, which is the reason it needs a zone ID. If anything was wrong with what you typed, it is listed with the rule number rather than silently corrected. And a kept from your input panel shows the port, zone identifier or prefix length that came in with the address, and where each one went back.
| What you see | What it means | What to do |
|---|---|---|
| Already canonical | Your address was already in RFC 5952 form. No rule fired. | Nothing. Use it as it is. |
| Rule 4.1 | A group had leading zeros, such as 0db8. | They are removed. Purely cosmetic, but required. |
| Rule 4.2.1 | A run of zero groups was written out and has been collapsed to ::. | Use the shorter form. |
| Rule 4.2.2 | :: stood for one zero group, which is not allowed. It has been written out. | Fix this in your config. Some parsers are strict about it. |
| Rule 4.2.3 | There was more than one run of zeros; the longest was chosen, or the first on a tie. | Nothing — this is the rule that makes the answer unique. |
| Rule 4.3 | Hex digits were uppercase. | Lowercase them. Uppercase is readable but not canonical. |
| Rule 5 | The last 32 bits were written as a dotted IPv4 address, because a well-known prefix says they are one. | Nothing. See the next section for when this applies. |
| Refused | The input is not a valid IPv6 address, and the reason says which part failed. | Fix the named part. The tool never guesses at what you meant. |
There are five, spread across three subsections of RFC 5952 section 4, and they are short enough to quote in full.
“Leading zeros MUST be suppressed.” So 2001:0db8::0001 is not acceptable and must be written 2001:db8::1. A group that is entirely zero becomes a single 0, not 0000 and not nothing.
“The use of the symbol ‘::’ MUST be used to its maximum capability.” If there is a run of zero groups, it gets collapsed. 2001:db8:0:0:0:0:2:1 must become 2001:db8::2:1.
This is the rule that gets broken, and it is the one worth remembering: “The symbol ‘::’ MUST NOT be used to shorten just one 16-bit 0 field.” The RFC supplies its own example — 2001:db8:0:1:1:1:1:1 is correct, and 2001:db8::1:1:1:1:1 is not.
It looks arbitrary until you see the reason. :: saves nothing here: 0 and :: are both one group wide in text. Allowing it would give one address two spellings of the same length, and the whole point of the document is that there is only ever one. A compressor that greedily replaces the first zero it finds gets this wrong on a very common shape of address.
“When there is an alternative choice in the placement of ‘::’ the longest run of consecutive 16-bit 0 fields MUST be shortened”, and where two runs are the same length, “the first sequence of zero bits MUST be shortened”. So 2001:0:0:1:0:0:0:1 becomes 2001:0:0:1::1 — the run of three wins over the run of two, even though the run of two comes first.
“The characters ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, and ‘f’ in an IPv6 address MUST be represented in lowercase.” That is the whole rule.
Where this bites in practice: an allowlist or a log filter that matches addresses as text. If your monitoring writes 2001:DB8::1 and your firewall rule says 2001:db8::1, nothing matches and nothing warns you. Canonicalising both ends is a one-time fix for a class of bug that is very hard to spot from either side.
Some IPv6 addresses carry an IPv4 address in their last 32 bits, and those are conventionally written with a dotted tail: ::ffff:192.0.2.1 rather than ::ffff:c000:201. RFC 5952 s5 permits that, but only under a condition worth reading carefully. The dotted form is recommended when “the address can be distinguished as having IPv4 addresses embedded in the lower 32 bits solely from the address field through the use of a well-known prefix”.
Two prefixes in the IANA registry meet that test, and this tool uses the dotted form for both:
::ffff:0:0/96 — IPv4-mapped (RFC 4291). How an IPv4 address is carried inside an IPv6 socket.64:ff9b::/96 — IPv4-IPv6 translation (RFC 6052). The well-known prefix a NAT64 gateway uses.A bare ::1.2.3.4 does not. Its only prefix is ::, which tells you nothing; it is not in the IANA special-purpose registry; and RFC 4291 s2.5.5.1 deprecated the form years ago. So the canonical answer here is hex: ::102:304.
And here is where implementations disagree with each other. The C library that ping, dig, ip addr and PHP all rely on prints a dotted tail for these addresses — ::192.0.2.1 rather than ::c000:201. Python’s ipaddress module, which is separate code, prints the hex form and agrees with this tool. Neither is a bug exactly: the C library follows a convention that predates the RFC’s recommendation. What matters is knowing, so where the C library really would print something different the result panel shows that form too and says so — and it does not claim a difference where there is none, so ::1 is simply ::1 everywhere.
If you are chasing an address that appears differently in two places for a different reason — a stale record rather than a formatting rule — our DNS lookup tool will show you the AAAA record a resolver is actually handing out.
Four steps, in order. Doing them in this order matters, because step three depends on having finished step two.
::, count the groups on each side and fill the middle with as many zero groups as it takes to reach eight.0db8 becomes db8; 0000 becomes 0.:: and lowercase everything.To check your answer, ask something that already implements the rules. Two worth knowing, because they are built on completely separate code: Python’s ipaddress module is pure Python, while PHP’s inet_ntop calls the system C library. Both were checked against every example in RFC 5952 before this page shipped, and both passed:
# Linux and macOS - print an address back at you, canonicalised
python3 -c "import ipaddress; print(ipaddress.IPv6Address('2001:0DB8:0:0:0:0:2:1'))"
# Same idea in PHP, which uses the system library
php -r 'echo inet_ntop(inet_pton("2001:0DB8:0:0:0:0:2:1")), PHP_EOL;'
# And the address a resolver hands out for a name
dig AAAA example.com +short
One thing to know before you trust either of them completely: on the deprecated ::a.b.c.d form the two disagree with each other. The C library prints a dotted tail, so php -r 'echo inet_ntop(inet_pton("::192.0.2.1"));' gives you ::192.0.2.1. Python gives ::c000:201 — the hex form, the same as this tool. On every other shape of address all three agree exactly: the comparison was run across 120,105 addresses against both, with no unexplained difference.
An address rarely arrives on its own. Three things get attached to it, each with its own rule, and all three are kept by this tool and put back around the compressed address.
A colon already separates groups inside the address, so 2001:db8::1:443 is genuinely ambiguous — is 443 a port or the last group? RFC 5952 s6 settles it with brackets: [2001:db8::1]:443. This is also the form a URL requires, which is why you see it in browser address bars.
Addresses in fe80::/10 are only meaningful on one link, and a machine with three interfaces can have the same link-local address on all three. The zone identifier says which: fe80::1%eth0 on Linux, fe80::1%en0 on macOS, and a numeric index such as fe80::1%12 on Windows. That is RFC 4007 s11. Inside a URL it has to be percent-encoded as %25eth0 (RFC 6874), which is why you sometimes see what looks like a stray 25.
2001:db8::/32 is a block of addresses, not one address. Compression works the same way, and the tool keeps the /32 attached. If you need to do arithmetic on the block rather than tidy its text, the subnet calculator works out ranges and host counts, and for the reasoning behind prefix sizes our piece on why IPv4 ran out and IPv6 matters explains why they are so large.
A quick way to tell a zone from a prefix: a zone ID follows a % and names an interface, so it only appears on link-local addresses. A prefix length follows a / and is a number from 0 to 128. They can appear together — fe80::1%eth0/64 is valid — and the tool handles that case.
Compression is a text operation, and it is worth being clear about what that does and does not tell you.
ipaddress module agreed on every single one, and the system C library agreed on all but the deprecated ::a.b.c.d form discussed above.::/96 range, so it never claims a difference that does not exist.Paste it above. The tool removes the leading zeros from every group, replaces the longest run of all-zero groups with ::, and lowercases the hex digits — the three things RFC 5952 requires. It then tells you which of those rules it had to apply, so if your address was already correct you get told that instead of being shown a change that did not happen.
Because one address has many valid spellings and only one canonical one. 2001:0db8:0000:0000:0000:0000:0000:0001, 2001:db8::1 and 2001:DB8:0:0:0:0:0:1 are the same 128 bits. Routers, logs and config files each pick their own style, which is why comparing two addresses as text gives the wrong answer. Compress both to canonical form first, then compare.
No. :: means “fill with zeros until the address is 128 bits long”, and two of them would be ambiguous — there would be no way to know how many zeros belong to each. RFC 4291 allows it once. This tool refuses an address with two and says so rather than guessing at what you meant.
Because RFC 5952 s4.2.2 forbids it: “The symbol ‘::’ MUST NOT be used to shorten just one 16-bit 0 field.” The RFC gives the example itself — 2001:db8:0:1:1:1:1:1 is correct and 2001:db8::1:1:1:1:1 is not. Both are two characters apart and both are read the same way by every parser, which is exactly why the rule exists: without it, one address would have two equally short spellings.
The longest one. If two runs are the same length, the first one. That is RFC 5952 s4.2.3, and it is the rule most hand-written compressors get wrong. 2001:db8:0:0:1:0:0:1 has two runs of two, so it becomes 2001:db8::1:0:0:1 — not 2001:db8:0:0:1::1, even though that is the same length.
Because they follow different rules for the old IPv4-compatible form. RFC 5952 s5 permits a dot-decimal tail only when a well-known prefix marks the address as carrying an embedded IPv4 — ::ffff:0:0/96 and 64:ff9b::/96 do, and this tool uses dots for both. A bare ::1.2.3.4 has no such prefix, is not in the IANA registry, and was deprecated by RFC 4291 s2.5.5.1 — so the canonical form here is hex. The C library behind ping, dig and PHP prints it with dots regardless. The result panel shows you both, and which tool gives which.
Not at all. Compression is only about how the same 128 bits are written down. 2001:db8::1 and 2001:0db8:0000:0000:0000:0000:0000:0001 are byte for byte identical, so anywhere that accepts one accepts the other. What compression changes is how readable it is, and whether two addresses can be compared as strings.
The brackets separate the address from a port, because a colon already means something inside an IPv6 address: [2001:db8::1]:443 is unambiguous where 2001:db8::1:443 is not. That is RFC 5952 s6. The %eth0 part is a zone identifier (RFC 4007 s11) and is needed on link-local addresses, which are only meaningful on one interface. This tool keeps both and puts them back around the compressed address.
The converters either side of this one, and the rest of the address-format family.
The expansion tool does the opposite job, and the two round-trip exactly.
Expand an IPv6 address