The complete IPv6 address is:
2001:0db8:0000:0000:0000:ff00:0042:8329
The compressed form is:
2001:db8::ff00:42:8329
I understand both when they are placed beside each other.
The trouble begins when I meet only the compressed form and must remember:
- how many groups disappeared
- whether
::may appear twice - whether one zero group should be compressed
- which zero run to choose
- whether zeros may be removed from the right side of a group
- where the port number begins
IPv6 text looks flexible because several textual forms can represent the same 128-bit value.
That flexibility is useful for input.
It is less useful for:
- logs
- searches
- diagrams
- configuration
- audits
- conversations between tired engineers
RFC 4291 defines the legal forms of IPv6 text representation. RFC 5952 adds a recommended canonical form so that systems generate one predictable spelling while continuing to accept other valid forms.
PARSING
accept every valid RFC 4291 spelling
GENERATING
use the RFC 5952 recommended spelling
This article begins with the ordinary colon-separated hexadecimal form. A valid IPv6 address may also end with a dotted-decimal IPv4 tail, which needs one additional counting rule later.
Compression changes how the address is written. It never changes the 128 bits underneath it.
When I forget a rule, I return to the full eight-group form and compress it again.
Begin with eight groups
In the ordinary hexadecimal form, an IPv6 address contains eight groups.
Each underlying group represents sixteen bits. In fully expanded form it contains exactly four hexadecimal digits; in ordinary legal text it may contain between one and four.
xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
For example:
2001:0db8:0000:0000:0000:ff00:0042:8329
Count the groups:
1 2 3 4 5 6 7 8
2001:0db8:0000:0000:0000:ff00:0042:8329
The colon separates groups.
It does not separate bytes.
Each group contains:
4 hexadecimal digits
×
4 bits per digit
=
16 bits
Eight groups therefore contain:
8 × 16 = 128 bits
RFC 4291 describes the preferred text form as eight 16-bit pieces written with one to four hexadecimal digits each. The fully expanded form used here pads every piece to four digits so that all 128 bits are visible.
Whenever a compressed hexadecimal address becomes confusing, my first objective is:
Restore eight group-equivalents.
Once eight groups are visible, the address usually stops behaving like a riddle.
Rule one: remove leading zeros inside a group
Within each 16-bit group, leading zeros may be removed.
0db8 → db8
0042 → 42
0007 → 7
0000 → 0
Therefore:
2001:0db8:0000:0000:0000:ff00:0042:8329
becomes:
2001:db8:0:0:0:ff00:42:8329
RFC 5952 says leading zeros should be suppressed and that an individual all-zero group should be written as 0 when it is not part of the selected :: run. It also recommends lowercase hexadecimal letters.
The word leading matters.
Zeros may be removed only from the left side of one group.
Correct:
0042 → 42
These changes would alter the numeric value:
4200 → 42
1040 → 14
a000 → a
The zeros on the right are significant.
Each group is a hexadecimal number.
Removing a leading zero is like writing:
0042
as:
42
Removing a trailing zero would be like turning:
4200
into:
42
That is not abbreviation.
That is a different number wearing the first number’s coat.
Rule two: compress one run of zero groups
One consecutive run of all-zero 16-bit groups may be replaced with:
::
Starting with:
2001:db8:0:0:0:ff00:42:8329
the run:
0:0:0
can be compressed.
2001:db8::ff00:42:8329
The double colon represents the missing all-zero groups.
RFC 4291 allows :: to represent omitted zero groups and permits it at the beginning, middle or end of an address. It may appear only once because a second occurrence would make the number of omitted groups ambiguous.
This is valid:
2001:db8::42
This is not:
2001::db8::42
With two double colons, I cannot determine how many zero groups belong to each gap.
2001 : ? zeros : db8 : ? zeros : 42
The address has become a hexadecimal accordion with two unlabelled bellows.
One of them must remain closed.
Canonical form does not compress one zero group
RFC 4291’s general syntax permits flexible valid representations.
RFC 5952’s canonical recommendation is stricter.
It says :: should not be used merely to replace one zero group.
Start with:
2001:db8:0:1:2:3:4:5
The canonical form remains:
2001:db8:0:1:2:3:4:5
Not:
2001:db8::1:2:3:4:5
The second form can be interpreted as a valid 128-bit address because the omitted section must expand to one zero group.
It is not the recommended canonical spelling.
The practical rule I keep is:
Use :: for a run of
at least two zero groups.
That keeps :: useful as a genuine compression marker rather than an alternative way to write every isolated 0.
Compress the longest zero run
An address may contain more than one run of zero groups.
Only one can become ::.
RFC 5952 says to compress the longest run.
Consider:
2001:0:0:1:0:0:0:1
There are two candidate runs:
2001:[0:0]:1:[0:0:0]:1
The second run is longer.
The canonical form is:
2001:0:0:1::1
Not:
2001::1:0:0:0:1
The second spelling compresses only two groups while leaving a longer run visible.
It still describes the same address when expanded correctly.
It is not the preferred generated form.
The canonical rule is trying to produce:
- the shortest practical representation
- one stable representation
- predictable search and comparison behaviour
Without that rule, two systems may continually rewrite the same address into different but valid strings.
When the runs tie, choose the leftmost
Consider:
2001:db8:0:0:1:0:0:1
It contains two equal runs:
2001:db8:[0:0]:1:[0:0]:1
Both runs contain two zero groups.
RFC 5952 says that when the longest runs are equal, the first run should be compressed.
The canonical form is:
2001:db8::1:0:0:1
Not:
2001:db8:0:0:1::1
My compressed rule set is therefore:
1. Find every consecutive run
of all-zero groups.
2. Ignore isolated single zero groups.
3. Choose the longest run.
4. If lengths tie,
choose the leftmost.
5. Replace that run with ::.
That is the part I keep forgetting because the address remains understandable whichever equal run I choose.
Canonical text is less concerned with whether I can decipher it.
It is concerned with every system choosing the same decipherable form.
Expansion is a counting exercise
Given:
2001:db8::ff00:42:8329
I count the visible groups.
Before :::
2001
db8
Two groups.
After :::
ff00
42
8329
Three groups.
Total visible groups:
2 + 3 = 5
An IPv6 address requires eight group-equivalents.
Therefore :: represents:
8 - 5 = 3 zero groups
The expanded address is:
2001:0db8:0000:0000:0000:ff00:0042:8329
Another example:
fd4a:91b2:7c30::25
Visible groups:
fd4a
91b2
7c30
25
Four groups.
Missing groups:
8 - 4 = 4
Expanded:
fd4a:91b2:7c30:0000:0000:0000:0000:0025
The complete validation rule is:
WITHOUT ::
exactly eight group-equivalents
must be present
WITH ::
fewer than eight group-equivalents
must be visible
missing zero groups
=
8 - visible group-equivalents
The phrase group-equivalents matters when a dotted-decimal IPv4 tail is present. In the ordinary hexadecimal form, each visible group counts as one.
:: must replace at least one all-zero group. It cannot represent nothing.
Therefore this is invalid:
1:2:3:4:5:6:7:8::
Eight groups are already visible, leaving no missing group for :: to restore.
The same count catches an address that is too short without compression:
2001:db8:1:2:3:4:5
Only seven groups are present and there is no ::.
This form is valid:
2001:db8::1:2:3:4:5
Seven groups are visible, so :: restores one zero group.
The formula works because :: appears only once and always represents one or more complete zero groups.
A dotted IPv4 tail counts as two groups
RFC 4291 also permits a mixed representation in which the low-order 32 bits are written as four decimal IPv4 octets.
For example:
::ffff:192.0.2.128
The dotted tail is not one 16-bit group.
It represents:
32 bits
=
two 16-bit group-equivalents
Count the visible contribution:
ffff
= one group-equivalent
192.0.2.128
= two group-equivalents
That gives:
1 + 2 = 3 visible group-equivalents
Therefore :: represents:
8 - 3 = 5 zero groups
The fully hexadecimal value is:
0000:0000:0000:0000:0000:ffff:c000:0280
RFC 5952 recommends mixed notation for recognised address types whose final 32 bits contain an embedded IPv4 address. When expanding or validating one of those forms, I count the dotted-decimal tail as two groups.
:: and ::1 are complete addresses
The unspecified IPv6 address contains eight zero groups.
0000:0000:0000:0000:0000:0000:0000:0000
Its compressed form is:
::
The loopback address contains seven zero groups followed by one:
0000:0000:0000:0000:0000:0000:0000:0001
Its compressed form is:
::1
Both forms are defined by the ordinary compression rules rather than being mysterious punctuation invented separately. RFC 4291 identifies :: as the unspecified address and ::1 as loopback.
For ::1, one group is visible.
1 visible group
Therefore :: represents:
8 - 1 = 7 zero groups
For ::, no groups are visible.
It represents all eight zero groups.
The rule still works at the extremes.
A prefix length is not another address group
An IPv6 prefix may be written as:
2001:db8:1234:5600::/56
The /56 says how many leading bits belong to the prefix.
It does not participate in the eight-group count.
Address:
2001:db8:1234:5600::
Prefix length:
56
To expand the address portion:
2001
db8
1234
5600
Four visible groups.
Therefore:
8 - 4 = 4 zero groups
Expanded:
2001:0db8:1234:5600:0000:0000:0000:0000/56
RFC 4291 defines IPv6 prefixes using ipv6-address/prefix-length, where the prefix length counts the leftmost contiguous bits. It also warns that leading zeros may be removed within a group, but trailing zeros must not be discarded because doing so changes the group’s value and therefore the prefix.
For example:
cd30
cannot be shortened to:
cd3
The first value ends in four zero bits.
The second does not.
Compression shortens text.
It must not quietly move the network boundary.
Lowercase is the canonical spelling
Hexadecimal accepts:
A B C D E F
and:
a b c d e f
They represent the same numeric values.
RFC 5952 recommends lowercase when generating canonical IPv6 text.
Therefore:
2001:DB8::ABCD
should normally be rendered as:
2001:db8::abcd
A parser should still accept legitimate uppercase input.
The output should be predictable.
This matters when addresses appear in:
- logs
- database keys
- configuration diffs
- search queries
- inventory systems
Case should not create the illusion that two addresses differ.
Ideally, the application should parse the address into a binary address type and compare its numeric value rather than comparing raw input strings.
Brackets separate an address from its port
IPv4 can be written with a port as:
192.0.2.42:443
That notation becomes ambiguous with IPv6 because colons already separate address groups.
This:
2001:db8::42:443
could be interpreted entirely as an IPv6 address.
To combine an IPv6 literal with a port, place the address inside square brackets:
[2001:db8::42]:443
The brackets are not part of the IPv6 address.
They delimit the address inside a URI or host-and-port expression. RFC 3986 defines bracketed IP literals in URI host syntax, while RFC 5952 notes the need for an unambiguous delimiter when an IPv6 address is combined with a port number.
For example:
https://[2001:db8::42]:8443/
contains:
address:
2001:db8::42
port:
8443
The square brackets are tiny traffic cones placed around the colons so the port knows where to queue.
A zone identifier is not compression
A link-local address may appear as:
fe80::1234%eth0
The address portion is:
fe80::1234
The zone identifier is:
eth0
It tells the local system which interface or link context should be used for the scoped address.
It is not part of the 128-bit IPv6 address and does not affect zero compression.
Zone identifiers have local meaning and commonly correspond to an interface name or index. They can be relevant to link-local unicast addresses and scoped multicast addresses.
RFC 4007 defines IPv6 zones and zone indexes. RFC 9844, published in August 2025, describes how supporting user interfaces should accept addresses such as fe80::1%eth0 and notes that the zone identifier is meaningful only on the local node.
When expanding:
fe80::1234%eth0
I separate the zone first.
address:
fe80::1234
zone:
eth0
Then expand only the address:
fe80:0000:0000:0000:0000:0000:0000:1234
The zone remains:
%eth0
The %eth0 form is scoped-address input for operating-system tools and other interfaces that support zone identifiers. RFC 9844 does not establish a general syntax for placing a zone identifier into an HTTP URL fetched by a web browser.
Mixing the concepts makes the address appear to have grown a ninth, particularly unruly group.
It has not.
I let Python check my spelling
Python’s standard ipaddress module can show both compressed and expanded forms.
from ipaddress import IPv6Address
address = IPv6Address(
"2001:0db8:0000:0000:0000:ff00:0042:8329"
)
print(address.compressed)
print(address.exploded)
The output is:
2001:db8::ff00:42:8329
2001:0db8:0000:0000:0000:ff00:0042:8329
The module exposes compressed and exploded representations for IPv6 addresses, making it useful for validating examples and normalising data before comparison.
This example uses the ordinary hexadecimal form. Mixed IPv4 notation and scoped addresses introduce additional display behaviour, so I test those formats separately when an application accepts them.
I use the tool after forming my own answer.
That gives me two useful signals.
My answer matches:
the rule is settling in.
My answer differs:
inspect the zero count,
longest run or tie-break.
The tool is not embarrassed when I forget whether the leftmost equal run wins.
It merely returns the canonical address with the calm patience of a librarian reshelving the same book.
My compact rule card
FULL ADDRESS
8 groups
4 hexadecimal digits per group
in fully expanded form
16 bits per group
LEADING ZEROS
Remove them inside each group.
0042 → 42
0000 → 0
ZERO COMPRESSION
Replace one consecutive run
of zero groups with ::.
CANONICAL CHOICE
Use a run of at least two groups.
Choose the longest run.
If tied, choose the leftmost.
FREQUENCY
:: may appear only once.
It must replace at least
one zero group.
GROUP COUNT
Without a double colon:
exactly eight group-equivalents.
With a double colon:
fewer than eight group-equivalents.
Missing groups
=
8 - visible group-equivalents
MIXED IPv4 FORM
A dotted-decimal IPv4 tail
represents the final 32 bits.
Count it as two 16-bit groups.
CASE
Use lowercase a-f.
PORT
[2001:db8::42]:443
ZONE
fe80::1%eth0
The zone is not part
of the 128-bit address.
That is the entire compression machine.
Questions I now ask
Groups
How many group-equivalents are visible?
Expansion
If :: appears, how many zero groups must it represent to reach eight?
Validity
Does :: replace at least one group, or are exactly eight groups present without it?
Mixed form
Is a dotted-decimal IPv4 tail being counted as two groups?
Leading zeros
Have zeros been removed only from the left of a group?
Zero run
Is :: replacing consecutive all-zero groups?
Frequency
Does :: appear no more than once?
Canonical form
Was the longest eligible zero run selected?
Tie
If two runs are equal, was the leftmost chosen?
Case
Are hexadecimal letters lowercase?
Prefix
Is /n being treated separately from the address?
Port
Is a literal IPv6 address enclosed in brackets before adding a port?
Zone
Is %eth0 or another zone identifier being treated as local interface context rather than address data?
These questions are quicker than staring at a compressed address and hoping the omitted zeros reveal themselves through atmosphere.
The mental model I am keeping
My earlier model was:
IPv6 compression
remove some zeros
replace some others with ::
try not to use too many colons
The stronger model is:
FULL IPv6 ADDRESS
│
▼
EIGHT GROUPS
│
┌────────────┴────────────┐
│ │
▼ ▼
REMOVE LEADING ZEROS FIND ZERO-GROUP RUNS
│ │
│ ▼
│ CHOOSE THE LONGEST
│ │
│ TIE → CHOOSE LEFTMOST
│ │
└────────────┬────────────┘
▼
REPLACE ONCE WITH ::
│
▼
LOWERCASE HEXADECIMAL
│
▼
CANONICAL TEXT FORM
Leading-zero removal shortens individual 16-bit groups.
:: shortens one selected run of complete zero groups.
Expansion restores enough group-equivalents to reach 128 bits.
Brackets, prefix lengths and zones belong to the surrounding notation, not the underlying address value.
IPv6 compression is not arbitrary once these rules are visible.
First shorten each group. Then shorten one run of groups. Never shorten the underlying value.
The full address contains eight 16-bit pieces.
Leading zeros may disappear from individual groups.
One longest sequence of all-zero groups may become ::.
Equal sequences send the prize to the leftmost contestant.
A dotted-decimal IPv4 tail accounts for the final two groups.
A port waits outside square brackets.
A zone identifier waits outside the address itself.
And whenever the colons begin multiplying in the dark, I expand the address back to eight groups and turn the lights on.
References and further reading
-
RFC 4291: IP Version 6 Addressing Architecture Defines the legal full, compressed and mixed IPv6 text representations, as well as prefix notation.
-
RFC 5952: A Recommendation for IPv6 Address Text Representation Defines the recommended canonical form, including leading-zero removal, maximum
::compression, longest-run selection, the leftmost tie-break and lowercase hexadecimal. -
RFC 3986: Uniform Resource Identifier Generic Syntax Defines bracketed IP literals in URI host syntax.
-
RFC 4007: IPv6 Scoped Address Architecture Defines scoped IPv6 addresses, zones, zone indexes and the textual
%zoneextension used by later guidance. -
RFC 9844: Entering IPv6 Zone Identifiers in User Interfaces Current guidance for entering scoped IPv6 addresses with local zone identifiers such as
%eth0. It obsoletes RFC 6874. -
Python
ipaddressmodule introduction Demonstrates parsing IP addresses and retrieving compressed and expanded IPv6 representations.