CIDR to IP Range: How to Convert a Prefix to Its First and Last Address

CIDR to IP Range: How to Convert a Prefix to Its First and Last Address

A CIDR block such as 192.168.4.0/22 is a compact way of writing a contiguous range of IP addresses. To convert it to an IP range, keep the first 22 bits of the address, set the remaining 10 bits to zero for the first address and to one for the last, and raise 2 to the power of 10 for the count. So 192.168.4.0/22 runs from 192.168.4.0 to 192.168.7.255 and contains 1,024 addresses.

If you only need the answer, paste the block into our CIDR to IP range converter. It handles IPv4 and IPv6 and does the reverse conversion too. The rest of this post is the math behind that result, so you can do it in your head during an incident, spot when a calculator gets an edge case wrong, and write it in a few lines of Python or JavaScript.

What the number after the slash means

An IPv4 address is 32 bits. The /22 says that the first 22 of those bits identify the network and are the same for every address in the block, while the last 10 vary from host to host. Written as a subnet mask, 22 ones followed by 10 zeros is 255.255.252.0. Both notations carry the same information. CIDR is shorter and, unlike a dotted mask, works unchanged on a 128-bit IPv6 address.

The prefix length is also what a router compares. When a packet for 192.168.6.200 arrives, the router checks whether the first 22 bits match 192.168.4.0 and ignores the rest. When several prefixes match, the longest one wins, which is why a /24 announced inside someone else’s /16 pulls traffic away from it. The Virtualizor hijack of late August 2026 relied on exactly that.

CIDR dates from September 1993, when RFC 1519 replaced class A, B and C networks, with their fixed /8, /16 and /24 boundaries, by prefixes of any length. The current specification is RFC 4632. That history is why people still call a /24 a “class C” three decades after the class system stopped mattering.

Converting a /22 in four steps

Take 192.168.5.37/22, deliberately written with an address that is not the first one in its block. You will meet this form constantly: ip addr prints an interface’s own address followed by the prefix length of the subnet it sits in, and firewall rules are often copied from there.

  1. Write the address in binary, or at least the octet where the prefix boundary falls. Twenty-two bits covers the first two octets (16 bits) plus 6 bits of the third. The third octet, 5, is 00000101.
  2. Build the mask: 22 ones, then 10 zeros. Per octet that is 255.255.252.0, because 11111100 is 252.
  3. AND the address with the mask to get the first address. 00000101 AND 11111100 keeps the top six bits and gives 00000100, which is 4. The fourth octet is zeroed entirely. First address: 192.168.4.0.
  4. OR the first address with the inverted mask, 0.0.3.255, to get the last address. 4 OR 3 is 7 and the fourth octet becomes 255. Last address: 192.168.7.255. Count: 2^10 = 1,024.

The shortcut most network engineers use skips the binary. Find the octet the boundary falls in and work out the block size there: 2 raised to the number of free bits in that octet. For a /22 the third octet has 6 fixed bits and 2 free ones, so blocks are 4 wide and start at multiples of 4: 0, 4, 8, 12 and so on. The value 5 falls in the block that starts at 4 and ends at 7. Done, no binary required.

octet 1 octet 2 octet 3 octet 4 First 192.168.4.0 1100000010101000000001 0000000000 Last 192.168.7.255 1100000010101000000001 1111111111 Mask 255.255.252.0 1111111111111111111111 0000000000 /22 boundary 22 network bits, fixed for every address in the block 10 host bits 2^10 = 1,024 addresses
192.168.4.0/22 bit by bit. The 22 network bits never change inside the block; the 10 host bits run from all zeros to all ones.

A /24 and a /27 work the same way. For 203.0.113.0/24 the boundary falls exactly on an octet, so the fourth octet runs from 0 to 255: the range is 203.0.113.0 to 203.0.113.255, 256 addresses. For 10.1.1.70/27, three bits of the fourth octet are fixed and five are free, so blocks are 32 wide and start at 0, 32, 64 and 96. The value 70 sits in the block starting at 64: the range is 10.1.1.64 to 10.1.1.95, 32 addresses.

CIDR string 192.168.5.37/22 Address 192.168.5.37 Prefix length 22 Mask: 22 ones, 10 zeros 255.255.252.0 First address 192.168.4.0 Last address 192.168.7.255 Count 2^(32-22) = 1,024 address AND mask first OR NOT mask 2^(32 - length)
The same conversion as a data flow. Every calculator, including ours, does exactly these three operations.

Prefix length to block size

PrefixSubnet maskAddressesUsable hosts on a LAN
/8255.0.0.016,777,21616,777,214
/12255.240.0.01,048,5761,048,574
/16255.255.0.065,53665,534
/20255.255.240.04,0964,094
/22255.255.252.01,0241,022
/24255.255.255.0256254
/25255.255.255.128128126
/26255.255.255.1926462
/27255.255.255.2243230
/28255.255.255.2401614
/29255.255.255.24886
/30255.255.255.25242
/31255.255.255.25422
/32255.255.255.25511

Each step down the table halves the block. Moving the prefix one bit longer always divides the count by two, which is also why a /23 is two /24s and a /22 is four of them.

The off-by-one traps

The usable-hosts column is where calculators disagree.

On an Ethernet segment the all-zeros host address names the network itself and the all-ones address is the broadcast address, which is why a /24 gives 254 hosts and a /27 gives 30. That is a convention about how hosts on a LAN use the block, not a property of the notation. A routing table, a firewall rule, an allowlist and a geolocation database all treat every one of the 256 addresses in a /24 as a member of the block. Cloud networks reserve even more: AWS keeps five addresses out of every VPC subnet, the first four and the last, so a /28 there yields 11 usable addresses rather than 14.

A /31 is the exception that trips up the LAN rule. RFC 3021, from December 2000, allows a two-address block on a point-to-point link with no broadcast address at all, so both addresses are usable. Router-to-router links use /31s everywhere. A calculator that reports zero usable hosts for a /31 is applying the subtract-two rule where it does not belong.

A /32 is a single address: first equals last, and the count is one. It is what a host route or a firewall rule for one server looks like.

At the other end, 0.0.0.0/0 covers all 4,294,967,296 IPv4 addresses. It is the default route, and it is the case that breaks naive code: in JavaScript a shift by 32 is silently a shift by 0, because shift counts are taken modulo 32. Python integers have no such limit.

The last trap is the one from the worked example: an address with host bits set. Python’s ipaddress.ip_network refuses 192.168.5.37/22 with “has host bits set” unless you pass strict=False. Our converter accepts it and returns the range of the enclosing block. Neither behavior is wrong, but if you feed a list of interface addresses to a tool, know which one you are getting.

IPv6: the same math on 128 bits

An IPv6 address is 128 bits written as eight 16-bit groups in hexadecimal, so a prefix length that is a multiple of 16 falls between groups and a multiple of 4 falls between hex digits. A /64 fixes the first four groups. For 2001:db8:1234:5678::/64 the first address is 2001:db8:1234:5678:: and the last is 2001:db8:1234:5678:ffff:ffff:ffff:ffff. The count is 2^64, or 18,446,744,073,709,551,616 addresses.

That number is not a mistake. RFC 4291 fixes the interface identifier at 64 bits, so a /64 is the standard size of a single subnet, and a home connection commonly gets a /56 to carve into 256 of them.

bits 0-15 bits 16-31 bits 32-47 bits 48-63 bits 64-79 bits 80-95 bits 96-111 bits 112-127 First 2001 0db8 1234 5678 0000 0000 0000 0000 Last 2001 0db8 1234 5678 ffff ffff ffff ffff /48 /56 /64 64-bit routing prefix, fixed 64-bit interface identifier 2^64 = 18,446,744,073,709,551,616 addresses
2001:db8:1234:5678::/64 split into 16-bit groups. A /48 boundary lands between groups; a /56 lands in the middle of the fourth group, which is where hand conversion goes wrong.

A boundary that is not a multiple of 16 needs a little more care. In 2001:db8:1234:5600::/56 the fourth group is 0x5600, and the /56 fixes its first 8 bits, the “56”, leaving the low byte free. The last address is therefore 2001:db8:1234:56ff:ffff:ffff:ffff:ffff, not 2001:db8:1234:5600:ffff:ffff:ffff:ffff.

Two more rules differ from IPv4. There is no broadcast address in IPv6, so the subtract-two convention does not exist, and /127 is the point-to-point equivalent of /31 per RFC 6164. You also have to expand the compressed form before doing any bit math, since :: stands for however many zero groups are missing and the position of every bit after it depends on that. The IPv6 expander does that step if you want to check your work.

The conversion in Python and JavaScript

Python’s standard library already does everything above, for both address families, including the reverse problem further down.

import ipaddress

net = ipaddress.ip_network("192.168.5.37/22", strict=False)
print(net.network_address, net.broadcast_address, net.num_addresses)
# 192.168.4.0 192.168.7.255 1024

net6 = ipaddress.ip_network("2001:db8:1234:5600::/56")
print(net6[0], net6[-1], net6.num_addresses)
# 2001:db8:1234:5600:: 2001:db8:1234:56ff:ffff:ffff:ffff:ffff 4722366482869645213696

JavaScript has no equivalent in the standard library, so here is the bit math for IPv4 written out. The >>> 0 casts are there because JavaScript’s bitwise operators work on signed 32-bit integers, and any address above 127.255.255.255 comes out negative without them.

function cidrToRange(cidr) {
  const [addr, len] = cidr.split('/');
  const hostBits = 32 - Number(len);
  const ip = addr.split('.').reduce((n, o) => (n << 8) + Number(o), 0) >>> 0;
  const first = hostBits === 32 ? 0 : ((ip >>> hostBits) << hostBits) >>> 0;
  const last = (first + 2 ** hostBits - 1) >>> 0;
  const dotted = n => [24, 16, 8, 0].map(s => (n >>> s) & 255).join('.');
  return { first: dotted(first), last: dotted(last), count: 2 ** hostBits };
}

cidrToRange('192.168.5.37/22');
// { first: '192.168.4.0', last: '192.168.7.255', count: 1024 }

For IPv6 in JavaScript, use BigInt throughout: the interface identifier alone is 64 bits, and 2^64 is far beyond Number.MAX_SAFE_INTEGER, which is 2^53 minus 1. This is why our converter returns the address count as a string and formats it client-side with BigInt. If you need the integer form of an address for a database column or a range comparison, the IP to number converter shows both directions.

The reverse problem: an IP range to the fewest CIDR blocks

A CIDR block must start at a multiple of its own size, so an arbitrary range rarely maps to a single prefix. The range 10.0.0.5 to 10.0.0.20 holds 16 addresses, yet it cannot be a /28, because a /28 would have to start at 10.0.0.0 or 10.0.0.16. Tiling it takes five blocks:

10.0.0.5/32
10.0.0.6/31
10.0.0.8/29
10.0.0.16/30
10.0.0.20/32

The algorithm is greedy. At the current start address, take the largest block that is aligned there and does not pass the end address, emit it, and continue from the address after it. Python exposes it as ipaddress.summarize_address_range(first, last), and the second form on the CIDR converter does the same thing in the browser. A friendlier example, 192.168.0.0 to 192.168.1.127, comes out as 192.168.0.0/24 plus 192.168.1.0/25.

You need this whenever a WHOIS record or a vendor hands you a range written as “from X to Y” and the firewall or prefix filter you are configuring only accepts prefixes.

Prefixes are the unit of routing, geolocation and allowlists

Almost nothing on the internet is described one address at a time. Regional registries allocate prefixes. BGP announces prefixes, and the global table is measured in them, not in addresses. A geofeed maps prefixes to locations. Cloud providers publish their address space as lists of prefixes. A firewall matches a packet against the longest prefix it knows.

Geolocation and hosting detection follow the same grain. Every Ipregistry lookup returns the routed prefix the address belongs to in connection.route, alongside the ASN that announces it, and our subnet pages such as 1.1.1.0/24 describe a whole block at once. Converting that prefix to a range is how you answer the practical question: is my address inside it? The Ipregistry API returns the prefix, the autonomous system and the location for any IPv4 or IPv6 address, and you can try it with 20,000 free lookups to get started.

Frequently asked questions

How do I convert a CIDR block to an IP range?

Build a mask with as many leading ones as the prefix length. AND the address with the mask to get the first address, OR the first address with the inverted mask to get the last, and raise 2 to the number of host bits for the count. For 192.168.4.0/22 that gives 192.168.4.0 to 192.168.7.255 and 1,024 addresses.

How many IP addresses are in a /24, a /22 and a /27?

A /24 holds 256 addresses, a /22 holds 1,024 and a /27 holds 32. On a LAN, subtract two for the network and broadcast addresses, so 254, 1,022 and 30 hosts. Routing tables, firewall rules and geolocation databases count every address in the block.

What is the IP range of a /31 or a /32?

A /32 is a single address, so the first and last address are the same. A /31 is two addresses, and since RFC 3021 both are usable on a point-to-point link because there is no broadcast address to reserve.

How do I convert an IP range back to CIDR notation?

Starting at the first address, take the largest block that starts there and does not pass the last address, then repeat from the next address. Python's ipaddress.summarize_address_range does this, and the Ipregistry CIDR converter has a range-to-CIDR form for IPv4 and IPv6.

Engage customers with in-app updates using Noticeable Keep users in the loop Ship release notes that get read. Try Noticeable

Get started with 20,000 free lookups: sign up