DEV Community

Cover image for Subnetting and CIDR Notation: A Practical Guide
FlatlinerAtheist | CLI-NERDS
FlatlinerAtheist | CLI-NERDS

Posted on Originally published at clinerds.com

Subnetting and CIDR Notation: A Practical Guide

Working out whether 192.168.1.130 is on the same network as 192.168.1.73/26 should take a few seconds. Guess wrong and you end up with a VPN range that swallows your LAN, or a firewall rule that matches far more than you meant. This guide shows how CIDR notation maps to bits, how to find network and broadcast addresses by hand, how to carve a /24 into subnets for a home lab, and how to check what your Linux hosts are configured with.

Bits, prefixes, and masks

An IPv4 address is a 32-bit number. Dotted-quad notation splits it into four 8-bit octets and writes each one in decimal, so every octet runs from 0 to 255.

192      .168      .1        .73
11000000  10101000  00000001  01001001
Enter fullscreen mode Exit fullscreen mode

The prefix length after the slash says how many of those 32 bits, counted from the left, identify the network. The remaining bits identify a host inside that network. In 192.168.1.73/26, the first 26 bits are the network part and the last 6 bits are the host part.

A subnet mask is the same information written as an address: network bits set to 1, host bits set to 0. A /26 is 26 ones followed by 6 zeros, which in the last octet is 11000000, or 192.

Prefix Mask Addresses Usable hosts
/24 255.255.255.0 256 254
/25 255.255.255.128 128 126
/26 255.255.255.192 64 62
/27 255.255.255.224 32 30
/28 255.255.255.240 16 14
/30 255.255.255.252 4 2
/31 255.255.255.254 2 2 (point-to-point only)
/32 255.255.255.255 1 1 (single host)

The octet values worth memorizing are 128, 192, 224, 240, 248, 252, 254, 255. Each step adds one network bit.

Wildcard masks

A wildcard mask is the bitwise inverse of the subnet mask: a 1 bit can be anything, a 0 bit must match. Subtract each mask octet from 255, so the wildcard for a /26 is 0.0.0.63. Cisco IOS uses wildcard masks in access lists and OSPF network statements, and typing a subnet mask there instead matches the wrong range.

access-list 10 permit 192.168.1.64 0.0.0.63
router ospf 1
 network 10.10.20.0 0.0.0.255 area 0
Enter fullscreen mode Exit fullscreen mode

The first line matches 192.168.1.64 through 192.168.1.127. The OSPF line enables OSPF on interfaces inside 10.10.20.0/24.

Finding the network and broadcast by hand

The one number you need is the block size: 256 minus the interesting mask octet (the one that is not 255 or 0). For a /26 that is 256 - 192 = 64, so subnets start at multiples of 64 in that octet.

Take 192.168.1.73/26 as the worked example:

  1. The block size is 64, so the boundaries in the last octet are 0, 64, 128, 192.
  2. 73 falls between 64 and 128, so the network address is 192.168.1.64.
  3. The broadcast address is one below the next boundary: 192.168.1.127.
  4. Usable hosts are everything in between: 192.168.1.65 to 192.168.1.126, which is 62 addresses.

So 192.168.1.130 sits in a different subnet (192.168.1.128/26), and traffic to it goes through a router.

The general formula for usable hosts is 2^(32 - prefix) - 2. You subtract two because the all-zeros host address names the network and the all-ones host address is the broadcast. A /27 gives 2^5 - 2 = 30, a /30 gives 2^2 - 2 = 2.

Two prefixes break that rule:

  • A /31 has two addresses and, under RFC 3021, both are usable on point-to-point links such as a router-to-router connection. With only two ends, nothing is reserved.
  • A /32 is a single address. You see it on loopback addresses, host routes, and WireGuard peer entries such as AllowedIPs = 10.10.99.2/32.

For prefixes /17 to /23 the same method applies to the third octet, and the fourth octet runs from 0 on the network address to 255 on the broadcast.

Private ranges you will run into

RFC 1918 reserves three blocks for private networks. They are not routed on the public internet, so you can use them freely inside your own network:

Block Range
10.0.0.0/8 10.0.0.0 to 10.255.255.255
172.16.0.0/12 172.16.0.0 to 172.31.255.255
192.168.0.0/16 192.168.0.0 to 192.168.255.255

Note that 172.16.0.0/12 stops at 172.31.255.255. An address like 172.32.0.1 is public.

A fourth range is 100.64.0.0/10 (100.64.0.0 to 100.127.255.255), reserved by RFC 6598 for carrier-grade NAT between an ISP and its customers. Tailscale also assigns device addresses from it. If your router's WAN address is in this range, you are behind CGNAT and inbound port forwarding from the internet will not work.

Splitting a /24 for a home lab

Splitting a network means borrowing host bits for the network part. Going from /24 to /26 borrows two bits, which gives 2^2 = 4 subnets of 64 addresses each:

192.168.1.0/26     hosts .1   to .62    broadcast .63
192.168.1.64/26    hosts .65  to .126   broadcast .127
192.168.1.128/26   hosts .129 to .190   broadcast .191
192.168.1.192/26   hosts .193 to .254   broadcast .255
Enter fullscreen mode Exit fullscreen mode

Equal sizes rarely match real needs. Variable Length Subnet Masking (VLSM) lets each subnet have its own prefix. The method is to list what each segment needs, add headroom, round up to a block size, and allocate the largest blocks first so every block starts on its own boundary.

Here is a plan for a home lab that has one /24 to work with, 192.168.10.0/24:

Segment Expected devices Subnet Usable range Broadcast
IoT up to 100 192.168.10.0/25 .1 to .126 .127
Servers up to 50 192.168.10.128/26 .129 to .190 .191
Guests up to 25 192.168.10.192/27 .193 to .222 .223
Management up to 10 192.168.10.224/28 .225 to .238 .239
Spare none yet 192.168.10.240/28 .241 to .254 .255

The blocks add up to exactly 256 addresses (128 + 64 + 32 + 16 + 16) and none overlap. Placing small blocks first scatters them across the boundaries larger blocks need, which wastes space. The spare block leaves room for a new segment without renumbering.

If you have plenty of private space, giving each VLAN its own /24 in 10.0.0.0/8 is simpler to read and leaves room to grow. The home lab networking article uses that layout. Carving a single /24 is what you do when space is limited, such as a small routed block from a provider or a site-to-site VPN where every range has to be unique.

Summarizing routes into a supernet

Summarizing goes the other way: adjacent networks are advertised as one shorter prefix, which means fewer routes and shorter firewall rules.

Four /24s from 192.168.0.0 to 192.168.3.0 share their first 22 bits:

192.168.0.0   third octet 000000 00
192.168.1.0   third octet 000000 01
192.168.2.0   third octet 000000 10
192.168.3.0   third octet 000000 11
Enter fullscreen mode Exit fullscreen mode

The first six bits of the third octet are identical, so the summary is 192.168.0.0/22 (mask 255.255.252.0), covering 192.168.0.0 to 192.168.3.255.

Summaries only stay exact when the networks line up. If you own only 192.168.1.0/24 and 192.168.2.0/24, the smallest single prefix that covers both is still 192.168.0.0/22, which also includes 192.168.0.0/24 and 192.168.3.0/24. Advertising that summary pulls in traffic for networks you do not have. In that case, keep two routes.

Checking your configuration on Linux

The ip command shows prefix lengths directly and behaves the same on Ubuntu 24.04 and Debian 12.

ip -4 addr
Enter fullscreen mode Exit fullscreen mode
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 192.168.1.73/26 brd 192.168.1.127 scope global dynamic enp1s0
Enter fullscreen mode Exit fullscreen mode

The brd value is the broadcast address the kernel calculated. If it does not match your plan, the prefix is wrong.

ip route
Enter fullscreen mode Exit fullscreen mode
default via 192.168.1.65 dev enp1s0 proto dhcp src 192.168.1.73 metric 100
192.168.1.64/26 dev enp1s0 proto kernel scope link src 192.168.1.73 metric 100
Enter fullscreen mode Exit fullscreen mode

The second line is the connected route for the local subnet. Anything outside it goes to the default gateway. To see the route the kernel picks for one destination:

ip route get 192.168.1.130
Enter fullscreen mode Exit fullscreen mode
192.168.1.130 via 192.168.1.65 dev enp1s0 src 192.168.1.73 uid 1000
    cache
Enter fullscreen mode Exit fullscreen mode

The via confirms that .130 is off-subnet for this host. Python's standard library can double-check your arithmetic:

python3 -c 'import ipaddress; n = ipaddress.ip_network("192.168.1.73/26", strict=False); print(n, n.netmask, n.broadcast_address, n.num_addresses - 2)'
Enter fullscreen mode Exit fullscreen mode
192.168.1.64/26 255.255.255.192 192.168.1.127 62
Enter fullscreen mode Exit fullscreen mode

For quick checks in the browser, the subnet calculator gives the network, broadcast, host range, and wildcard mask for any prefix.

IPv6 subnet sizes

IPv6 uses the same prefix notation, but the practical choices are narrower. A LAN segment should be a /64, because stateless address autoconfiguration (SLAAC) builds addresses from a 64-bit interface identifier. A longer prefix such as a /80 breaks SLAAC on that segment.

ISPs commonly delegate a /48 or a /56 to a customer, usually through DHCPv6 prefix delegation. A /56 contains 256 /64 subnets and a /48 contains 65,536, so you plan IPv6 by counting segments rather than hosts.

IPv6 has no broadcast address. Functions that used broadcast in IPv4, such as address resolution, use multicast and Neighbor Discovery instead, so you do not subtract two addresses per subnet.

Common mistakes

Overlapping subnets cause the classic "it works until I connect the VPN". If your home network and a VPN both use 192.168.1.0/24, the machine cannot route to both. Docker adds another trap: its default bridge uses 172.17.0.0/16, and user-defined networks take further private ranges. A LAN or VPN in those ranges becomes unreachable from the Docker host because the kernel sends that traffic to a bridge. Check it:

ip route get 172.17.5.10
Enter fullscreen mode Exit fullscreen mode

If the output shows dev docker0 for an address that should be on your LAN, move Docker's ranges in /etc/docker/daemon.json:

{
  "bip": "10.200.0.1/24",
  "default-address-pools": [
    { "base": "10.201.0.0/16", "size": 24 }
  ]
}
Enter fullscreen mode Exit fullscreen mode
sudo systemctl restart docker
Enter fullscreen mode Exit fullscreen mode

Restarting Docker stops running containers (unless live-restore is enabled), and existing user-defined networks keep their old subnets until you recreate them (for Compose projects, docker compose down then docker compose up -d). Do it in a maintenance window.

Off-by-one errors on the broadcast address come from adding the block size instead of block size minus one. The broadcast for 192.168.1.64/26 is .127, not .128. Assigning .128 to a host puts it in the next subnet.

Using .0 or .255 of a /24 as a host address fails because those are the network and broadcast addresses. In a larger subnet such as a /23, an address ending in .255 or .0 in the middle of the range can be a valid host, so check the prefix before assuming.

A mask mismatch on one segment causes traffic that works in one direction only. A host configured as /24 sends straight to an address that a /26 host treats as remote, so the replies head for a gateway instead. Every host and the router interface on a segment need the same prefix.

Before you commit a plan

Top comments (0)