<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yuanke215</title>
    <description>The latest articles on DEV Community by yuanke215 (@yuanke215).</description>
    <link>https://dev.to/yuanke215</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4145007%2Fd96ca73a-da4a-4ab1-8297-d98d3a66cbb6.png</url>
      <title>DEV Community: yuanke215</title>
      <link>https://dev.to/yuanke215</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuanke215"/>
    <language>en</language>
    <item>
      <title>I Wasted Half My /24s Before I Learned VLSM</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:47:53 +0000</pubDate>
      <link>https://dev.to/yuanke215/i-wasted-half-my-24s-before-i-learned-vlsm-2jp4</link>
      <guid>https://dev.to/yuanke215/i-wasted-half-my-24s-before-i-learned-vlsm-2jp4</guid>
      <description>&lt;p&gt;I ran an office network. The engineering floor had 60 machines, marketing had 25, finance had 15, and the executive floor had 4. Each team "just needs its own subnet," so I handed out four fresh /24s. That's 4 x 254 usable addresses — more than 1,000 — for a building with about 100 machines total. I was using roughly 10% of what I allocated and calling it good practice.&lt;/p&gt;

&lt;p&gt;The same scene repeats everywhere: a /24 with 20 devices on it, a /26 holding 15, a VLAN with a printer and a door controller. Each one feels harmless, until you actually run out of address space and have to renumber the whole floor.&lt;/p&gt;

&lt;p&gt;The fix is VLSM — Variable Length Subnet Masking. And it's a one-sentence rule once you see the word.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-sentence rule
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;VLSM means different subnets inside the same network can use different prefix lengths, so each subnet is sized to what it actually contains — instead of forcing every subnet to share the same width.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without VLSM, if you're working inside 192.168.0.0/24, every subnet you carve out of it has the same size. Two /25s, each 126 usable addresses. A team of 15 gets 126 addresses and the other 111 sit idle. Then you run out of /24s and wonder where all the space went.&lt;/p&gt;

&lt;p&gt;With VLSM, you mix the sizes freely: one /26 (62 usable) for engineering's 60 machines, a /27 (30 usable) for the 25-person team, another /27 (30 usable) for the 15-person team, and a /28 (14 usable) for the executive floor — all carved from a single /24.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example (this is the whole trick)
&lt;/h2&gt;

&lt;p&gt;Let's split 192.168.10.0/24, which gives 254 usable addresses, into four segments: 60 devices, 25 devices, 15 devices, and 4 devices.&lt;/p&gt;

&lt;p&gt;For each, round the host count up to the next power of two, then subtract 2 for the network and broadcast addresses, then pick the prefix that matches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Segment&lt;/th&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Addresses&lt;/th&gt;
&lt;th&gt;Usable hosts&lt;/th&gt;
&lt;th&gt;Assign to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.0&lt;/td&gt;
&lt;td&gt;/26&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;Engineering (60)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.64&lt;/td&gt;
&lt;td&gt;/27&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;Marketing (25)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.96&lt;/td&gt;
&lt;td&gt;/27&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;Finance (15)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.10.128&lt;/td&gt;
&lt;td&gt;/28&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Exec (4)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Add up the usable hosts: 62 + 30 + 30 + 14 = 136 total — still barely half of the 254 the /24 could hold, but every team gets only as much as it needs.&lt;/p&gt;

&lt;p&gt;The subtle part: VLSM doesn't mean "make every subnet as small as possible." It means "choose each subnet's size from the same /24 based on its need."&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick reference for picking the prefix
&lt;/h2&gt;

&lt;p&gt;You don't want to redo the subtraction every time you plan a segment, so here's the lookup I actually use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hosts needed&lt;/th&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Usable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;/30&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;/29&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;/28&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;/27&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;/26&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;/25&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;254&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;254&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To read it: "I need room for 60 machines" → 60 fits in the 64-size block → that's a /26, giving 62 usable.&lt;/p&gt;

&lt;p&gt;That's the whole skill: memorize the "next power of two minus 2" ladder, and VLSM becomes table lookup instead of head-scratching.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should NOT use VLSM
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Routing gets more complicated.&lt;/strong&gt; Every width you mix in means the router carries more entries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future growth is harder.&lt;/strong&gt; If "25 devices" becomes "80 next quarter," you're renumbering that subnet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The next person has to read your mind.&lt;/strong&gt; The cleverest allocation is the hardest to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're still learning the basic power-of-two math.&lt;/strong&gt; Flat equal-sized subnets are a fine starting point.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Build the plan without doing arithmetic in your head
&lt;/h2&gt;

&lt;p&gt;When you're laying out several real segments, the subtraction slips — a /26 becomes a /28, or two blocks overlap. I keep a &lt;a href="https://ipcalcplus.com/vlsm-calculator.html" rel="noopener noreferrer"&gt;VLSM calculator&lt;/a&gt; that takes the network and the list of host counts, and returns a minimal, non-overlapping layout, showing the network, usable range, and hosts for each segment side by side. It's free, runs entirely in the browser, and makes the plan verifiable instead of vibes.&lt;/p&gt;

&lt;p&gt;For the longer reasoning — why variable-length works and a fuller worked example — I've written it up in &lt;a href="https://ipcalcplus.com/vlsm-explained.html" rel="noopener noreferrer"&gt;VLSM explained&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The takeaway is short: the /24 you give to a 4-person team isn't "playing it safe" — it's burning the exact thing that eventually runs out. VLSM keeps one clean address budget, gives each segment only what it needs, and leaves room for the next request. It stopped me from asking for "another /24 for five people," and it'll save your address space the same way.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>devops</category>
      <category>sysadmin</category>
      <category>cloud</category>
    </item>
    <item>
      <title>An IPv4 Admin's First Hour with IPv6 Subnets</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:07:25 +0000</pubDate>
      <link>https://dev.to/yuanke215/an-ipv4-admins-first-hour-with-ipv6-subnets-37f7</link>
      <guid>https://dev.to/yuanke215/an-ipv4-admins-first-hour-with-ipv6-subnets-37f7</guid>
      <description>&lt;p&gt;If you've spent a decade in IPv4, the first time you look at an IPv6 subnet you'll feel a strong urge to do something deeply wrong: try to carve it up "efficiently."&lt;/p&gt;

&lt;p&gt;Here's the trap. After years of VLANs and squeezing every last usable host out of a /24, the instinct is burned in — address space is precious, allocate it sparingly, never waste a bit. So someone hands you a /64, which contains more addresses than the entire IPv4 internet, and your hand reaches for the subnet calculator before your brain catches up.&lt;/p&gt;

&lt;p&gt;Stop. That is the wrong tool for this job, and the mistake you are about to make is one of the most common (and most expensive to undo) things an IPv4 network admin does when they first touch IPv6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your VLSM instinct is wrong on IPv6
&lt;/h2&gt;

&lt;p&gt;In IPv4, subnetting exists to conserve a scarce resource. Every /24 you waste is a /24 you cannot give to the next department, so you subnet down to the last bit, allocating the tightest prefix that fits.&lt;/p&gt;

&lt;p&gt;IPv6 inverts that logic. A /64 from your ISP's allocation is not waste — it is the &lt;em&gt;expected minimum size&lt;/em&gt;. And here is the detail that catches most people: &lt;strong&gt;the /64 is not an abstraction. For most hosts it is a hard requirement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This comes down to SLAAC, the Stateless Address Autoconfiguration mechanism. When an IPv6 host joins a network, it does not wait for a DHCP lease the way most IPv4 hosts do. It listens for a Router Advertisement, reads the 64-bit network prefix it contains, and then builds its own address by combining that prefix with a 64-bit interface identifier (often derived from its hardware address).&lt;/p&gt;

&lt;p&gt;The "64" in /64 is not a coincidence. An IPv6 address is 128 bits, split exactly in half: 64 bits of network, 64 bits of interface. If you assign a prefix &lt;strong&gt;longer&lt;/strong&gt; than /64 (a /72, an /80, anything carved further), a host cannot run SLAAC at all — there are not enough bits left to construct its own interface identifier. Devices that depend on SLAAC will simply fail to configure, and you'll spend an afternoon diagnosing "why won't anything on this segment get a network address" before realizing you handed it a /72.&lt;/p&gt;

&lt;p&gt;So the mental model you need is almost the exact reverse of your IPv4 habits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;IPv4&lt;/th&gt;
&lt;th&gt;IPv6&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default subnet is /24&lt;/td&gt;
&lt;td&gt;Default subnet is /64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subnetting conserves a scarce resource&lt;/td&gt;
&lt;td&gt;Subnetting rarely needs to conserve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You split to the smallest usable size&lt;/td&gt;
&lt;td&gt;You almost never go below /64 for a segment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Host address usually comes from DHCP&lt;/td&gt;
&lt;td&gt;Host usually builds its own (SLAAC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Being wrong wastes addresses&lt;/td&gt;
&lt;td&gt;Being wrong breaks connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The three traps IPv4 admins catch first
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Trap 1: Treating /64 as "wasteful" and subnetting below it.&lt;/strong&gt; You cannot make a /64 into two /65s and call it more efficient — you've just broken SLAAC for every client that needs it. If you have a real need for smaller segments, those prefixes are for router-to-router links, loopbacks, and special cases, not for a normal LAN with hosts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 2: Assuming the interface portion is a meaningful "host number."&lt;/strong&gt; In IPv4 you look at the last octet and think "host 5." In IPv6 the interface identifier is an arbitrary 64-bit value, often randomized for privacy (which is why a device's address can change after a reboot). You cannot plan around stable, sequential host numbers the way you did with IPv4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 3: Trying to "save addresses" the way you saved a /24.&lt;/strong&gt; You are not conserving anything. If your ISP gives you a single /64 and you need multiple networks, the real fix is a renumbering plan or a larger allocation from your provider — not cramming multiple LANs into one /64 and calling it subnetting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a correct IPv6 plan actually looks like
&lt;/h2&gt;

&lt;p&gt;Here is the shape of an IPv6 deployment that doesn't blow up on you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your ISP allocates a /48 or /56 — the "normal size" block, big enough to be boring.&lt;/li&gt;
&lt;li&gt;You split that into one &lt;strong&gt;/64 per VLAN&lt;/strong&gt;, exactly the way you'd give each VLAN its own /24 in IPv4.&lt;/li&gt;
&lt;li&gt;Each segment gets a clean, memorable prefix from your allocation (for example &lt;code&gt;2001:db8:1::/64&lt;/code&gt; for the office LAN, &lt;code&gt;2001:db8:2::/64&lt;/code&gt; for the server segment).&lt;/li&gt;
&lt;li&gt;You let SLAAC handle host addressing. Servers that need DHCPv6 can get static or DHCP-assigned addresses, but the subnet boundary stays /64.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb that makes this click: &lt;strong&gt;in IPv6, /64 is your /24.&lt;/strong&gt; It is the default, the boring, the correct choice. When someone asks "what prefix should I use for this segment," the overwhelming answer is /64 — and the interesting part of your job is deciding &lt;em&gt;which&lt;/em&gt; /64, not &lt;em&gt;how small&lt;/em&gt; it can be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your work with a calculator
&lt;/h2&gt;

&lt;p&gt;The math slips fast when you're new — you transpose a /64 and a /56, or you're not sure whether &lt;code&gt;2001:db8:1::/64&lt;/code&gt; includes the range you think it does. I built an &lt;a href="https://ipcalcplus.com/ipv6-subnet-calculator.html" rel="noopener noreferrer"&gt;IPv6 subnet calculator&lt;/a&gt; that takes an IPv6 address and prefix and returns the network address, first and last usable addresses, and the total /64 count — handy for verifying a segment against your plan.&lt;/p&gt;

&lt;p&gt;For the full mental shift — why the /64 is required, the SLAAC rule, and a worked example — I wrote it up in &lt;a href="https://ipcalcplus.com/ipv6-subnetting-for-ipv4-admins.html" rel="noopener noreferrer"&gt;IPv6 Subnetting for IPv4 Network Admins&lt;/a&gt;. It's the page I wish I'd had the first time I planned an IPv6 office.&lt;/p&gt;

&lt;p&gt;The one-sentence version: &lt;strong&gt;stop trying to be efficient with IPv6. The address space is not the thing you're fighting; clean routing is.&lt;/strong&gt; Embrace the /64, assign one per segment, let SLAAC do the rest, and your first hour with IPv6 subnets goes from a pile of confusion to a boring, correct default.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>ipv6</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Hash You Pick Matters: MD5, SHA-1, SHA-256, and What Each Is Still Good For</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:23:09 +0000</pubDate>
      <link>https://dev.to/yuanke215/the-hash-you-pick-matters-md5-sha-1-sha-256-and-what-each-is-still-good-for-3c42</link>
      <guid>https://dev.to/yuanke215/the-hash-you-pick-matters-md5-sha-1-sha-256-and-what-each-is-still-good-for-3c42</guid>
      <description>&lt;p&gt;You download a 2 GB ISO from a mirror you have never heard of, the page shows a long checksum, and you skip it. Six months later, that same image is implicated in a supply-chain incident and your team spends a weekend rebuilding machines.&lt;/p&gt;

&lt;p&gt;Checksums exist precisely to catch this. But the first question is not "how do I verify a hash" — it is "which hash am I supposed to be looking at, and is that hash still trustworthy?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What a hash actually is
&lt;/h2&gt;

&lt;p&gt;A hash function takes any input — a text string, a firmware image, a password — and produces a fixed-size fingerprint. Same input, same fingerprint. Change one bit of input and the fingerprint changes completely (or it should). MD5 always outputs 128 bits, SHA-256 always outputs 256 bits, regardless of input size.&lt;/p&gt;

&lt;p&gt;The properties that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic&lt;/strong&gt;: the same bytes always produce the same digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preimage resistant&lt;/strong&gt;: given a digest, you cannot reconstruct the input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collision resistant&lt;/strong&gt;: you cannot find two different inputs with the same digest (this is the one that has been broken for old algorithms).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The family tree, and what it means in practice
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Use today&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MD5&lt;/td&gt;
&lt;td&gt;128-bit / 32 hex&lt;/td&gt;
&lt;td&gt;Collision-broken (since 2004–2008)&lt;/td&gt;
&lt;td&gt;Legacy checksums, dedup, cache keys — never security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;160-bit / 40 hex&lt;/td&gt;
&lt;td&gt;Collision-broken (2017 SHAttered)&lt;/td&gt;
&lt;td&gt;Deprecated; avoid for anything new&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256&lt;/td&gt;
&lt;td&gt;256-bit / 64 hex&lt;/td&gt;
&lt;td&gt;Secure&lt;/td&gt;
&lt;td&gt;Default choice: downloads, TLS, Git, certificates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-384 / SHA-512&lt;/td&gt;
&lt;td&gt;384/512-bit&lt;/td&gt;
&lt;td&gt;Secure&lt;/td&gt;
&lt;td&gt;High-security contexts, longer hashes for HMAC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The nuance that surprises people: &lt;strong&gt;MD5 is not "useless" — it is useless for security&lt;/strong&gt;. It is still perfectly fine for spotting accidental corruption, deduplicating files, or as a cache key. The breakage is about &lt;em&gt;deliberately&lt;/em&gt; constructing a collision, which is an attack, not an accident.&lt;/p&gt;

&lt;p&gt;Meanwhile SHA-1's collision break (two different PDFs with the same hash, demonstrated in 2017) means it is now publicly deprecated: browsers, Git, and operating systems have been migrating away from it for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where hashes actually protect you day to day
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Download integrity — the one you should do every time.&lt;/strong&gt;&lt;br&gt;
Every official Linux ISO page, every tool release on GitHub, publishes the SHA-256 checksum. Verifying takes seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Official download from the project's checksum file&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;downloaded-file.iso
&lt;span class="c"&gt;# compare the output against the published value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;download.iso&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;65536&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the digest matches, the bytes you have are byte-for-byte identical to what the maintainer published. That is not the same as "safe" (you also need the checksum itself to come from a trusted channel) but it kills the entire class of "corrupted or swapped download" accidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Passwords — where you should NOT use these.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the biggest hash misconception: passwords should not be hashed with MD5 or SHA-256 at all. They are fast, and fast is exactly what an attacker wants when brute-forcing. Password storage needs deliberately slow, salted functions: &lt;code&gt;bcrypt&lt;/code&gt;, &lt;code&gt;argon2&lt;/code&gt;, or &lt;code&gt;scrypt&lt;/code&gt;. If you see a tutorial storing passwords as plain MD5 or SHA-256, it is teaching a vulnerability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. File dedup and change detection.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hashes are the cheapest reliable "did this change" signal: config-drift checks, backup deduplication, registry of golden files. Collision risk is not a concern here because the input is not adversarial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Git, signatures, containers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Git commit IDs are SHA-1 (being slowly moved to SHA-256); software packages are signed with SHA-256 and then cryptographically signed; container images get digest pins. Same idea everywhere: a short fingerprint that is expensive to fake.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 30-second way to sanity-check your own values
&lt;/h2&gt;

&lt;p&gt;When you are debugging a mismatch — "the docs say the hash is X, but I keep getting Y" — the first thing to check is whether your tool produces the same digest for the exact same bytes. A &lt;a href="https://ipcalcplus.com/hash-calculator.html" rel="noopener noreferrer"&gt;hash calculator&lt;/a&gt; that runs MD5, SHA-1, SHA-256, SHA-384 and SHA-512 locally in the browser is handy for this: paste the string, check the output, and confirm whether the discrepancy is in the input (trailing newline, BOM, whitespace) or in the algorithm (mixing MD5 and SHA-256). The page never sends your data to a server, so it is safe for things like API keys and config snippets.&lt;/p&gt;

&lt;p&gt;A trailing newline is the classic silent killer: &lt;code&gt;echo "hello" | sha256sum&lt;/code&gt; and &lt;code&gt;printf "hello" | sha256sum&lt;/code&gt; give different digests. Always verify with the exact bytes of the file — &lt;code&gt;sha256sum&lt;/code&gt; on the file itself, not a copy-paste of its contents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules of thumb
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;For anything security-related (signatures, TLS, authentication): use SHA-256 or stronger. Never MD5, never SHA-1.&lt;/li&gt;
&lt;li&gt;For accidental-corruption detection (backups, downloads you wrote yourself): MD5 is still fine, but just use SHA-256 — it costs the same and removes the question entirely.&lt;/li&gt;
&lt;li&gt;For passwords: bcrypt/argon2/scrypt, with a salt. Do not roll your own.&lt;/li&gt;
&lt;li&gt;When a checksum mismatch appears: re-download first, then check the algorithm, then check for extra bytes (newline/BOM) — in that order.&lt;/li&gt;
&lt;li&gt;Never trust a checksum served from the same server as the file, for anything that matters. Get it from the official channel.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The habit of verifying hashes costs 30 seconds and prevents the nastiest category of "I downloaded something once, and it was wrong" incidents. Start with the next big download you do this week — and notice how many releases still publish an old MD5 instead of SHA-256. That is a signal about the project's security posture all by itself.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>python</category>
    </item>
    <item>
      <title>Stop Storing IP Addresses as Strings: The Integer Trick That Makes Your Queries 10x Faster</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:19:46 +0000</pubDate>
      <link>https://dev.to/yuanke215/stop-storing-ip-addresses-as-strings-the-integer-trick-that-makes-your-queries-10x-faster-119a</link>
      <guid>https://dev.to/yuanke215/stop-storing-ip-addresses-as-strings-the-integer-trick-that-makes-your-queries-10x-faster-119a</guid>
      <description>&lt;p&gt;Your log table has 80 million rows. The &lt;code&gt;client_ip&lt;/code&gt; column is a &lt;code&gt;VARCHAR(15)&lt;/code&gt;, indexed, and every analytics query that filters by IP range takes three seconds. The DBA shrugs: "that's just how IPs are stored."&lt;/p&gt;

&lt;p&gt;It isn't. An IPv4 address is a 32-bit unsigned integer — &lt;code&gt;192.168.1.1&lt;/code&gt; is literally &lt;code&gt;0xC0A80101&lt;/code&gt; is literally &lt;code&gt;3232235777&lt;/code&gt;. Every time you store it as a string, you pay the price in storage, index size, and range-query performance. And you pay it again on every single query.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number hiding inside every IPv4 address
&lt;/h2&gt;

&lt;p&gt;An IPv4 address is four octets, each 8 bits, concatenated into one 32-bit number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.1

octet 1: 192 = 11000000
octet 2: 168 = 10101000
octet 3:   1 = 00000001
octet 4:   1 = 00000001

32-bit value = 11000000101010000000000100000001
decimal      = 3232235777
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The math: &lt;code&gt;192 × 256³ + 168 × 256² + 1 × 256 + 1 = 3232235777&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That single integer is &lt;em&gt;all&lt;/em&gt; the information in the address. Everything else — dotted decimal, hex, the network and host portions — is a view of those same 32 bits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What string storage costs you
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;&lt;code&gt;VARCHAR(15)&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;INT UNSIGNED&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storage per row&lt;/td&gt;
&lt;td&gt;15 bytes (or more with utf8mb4)&lt;/td&gt;
&lt;td&gt;4 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index size&lt;/td&gt;
&lt;td&gt;large, poor locality&lt;/td&gt;
&lt;td&gt;compact, sequential&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range query (&lt;code&gt;BETWEEN&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;string comparison, lexicographic surprises&lt;/td&gt;
&lt;td&gt;native integer comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Membership check&lt;/td&gt;
&lt;td&gt;regex/full scan territory&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BETWEEN&lt;/code&gt; on an index&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The lexicographic trap is the sneaky one. &lt;code&gt;SELECT ... WHERE client_ip BETWEEN '10.1.0.1' AND '10.1.0.255'&lt;/code&gt; looks right but string comparison orders &lt;code&gt;'10.1.0.255' &amp;lt; '10.1.1.0'&lt;/code&gt; — while &lt;code&gt;'10.2.0.1' &amp;lt; '10.1.0.255'&lt;/code&gt; is also true, so a naive lexicographic range silently includes &lt;code&gt;10.2.x.x&lt;/code&gt; addresses. Every "why is my IP filter returning junk" ticket traces back to this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it right in MySQL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Store the integer, index it, done&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;access_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;        &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_ip&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- 4 bytes, not 15&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_client_ip&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Insert&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;access_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INET_ATON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.1.1'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;-- Range query: every address in 192.168.1.0/24&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;access_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;client_ip&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;INET_ATON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.1.0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;INET_ATON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'192.168.1.255'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Read it back&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;INET_NTOA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;access_log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;INET_ATON&lt;/code&gt; and &lt;code&gt;INET_NTOA&lt;/code&gt; are built into MySQL and MariaDB, use the index, and are deterministic. The only migration cost is one schema change and one bulk update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;access_log&lt;/span&gt; &lt;span class="k"&gt;MODIFY&lt;/span&gt; &lt;span class="n"&gt;client_ip&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;access_log&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;client_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INET_ATON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;-- careful: needs a plan for live tables&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(For live tables, do it in a staged fashion: add a new column, backfill in batches, swap, drop the old one.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What about PostgreSQL, Python, and IPv6?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; has first-class &lt;code&gt;INET&lt;/code&gt; and &lt;code&gt;CIDR&lt;/code&gt; types that sort and index properly — use them, they beat both &lt;code&gt;TEXT&lt;/code&gt; and naive integer storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;: &lt;code&gt;ipaddress&lt;/code&gt; module handles conversions cleanly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ipaddress&lt;/span&gt;

&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipaddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;192.168.1.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;          &lt;span class="c1"&gt;# 3232235777
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ipaddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3232235777&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 192.168.1.1
&lt;/span&gt;
&lt;span class="c1"&gt;# Whole subnet bounds as integers — trivial to turn into a BETWEEN query
&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipaddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ip_network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;192.168.1.0/24&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;network_address&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast_address&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IPv6&lt;/strong&gt;: 128 bits, so the string-vs-int tradeoff is even worse. Store as &lt;code&gt;BINARY(16)&lt;/code&gt; (or a &lt;code&gt;DECIMAL(39,0)&lt;/code&gt; / &lt;code&gt;VARBINARY&lt;/code&gt; in engines without one) — never as 39-character text if you can avoid it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When you actually need the dotted form
&lt;/h2&gt;

&lt;p&gt;Application logs and debugging always want &lt;code&gt;192.168.1.1&lt;/code&gt;. Conversion at read time is cheap: &lt;code&gt;INET_NTOA&lt;/code&gt; on the way out costs microseconds per row, far less than the string storage costs you on every write and every range scan.&lt;/p&gt;

&lt;p&gt;If you are doing this conversion by hand — subnet to bounds, or bounds back to dotted decimal — the &lt;a href="https://ipcalcplus.com/ip-converter.html" rel="noopener noreferrer"&gt;IP Address Converter&lt;/a&gt; on IPCalcPlus converts IPv4 addresses between dotted decimal, binary, hex, octal, and integer formats and expands a CIDR block to its integer range. That is the exact arithmetic you would otherwise do twice for each subnet in a migration script. No data leaves the browser, so you can throw your real production ranges at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist for your next migration
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Identify every &lt;code&gt;VARCHAR&lt;/code&gt; column that stores an IP (logs, user sessions, rate-limit tables, firewall rules).&lt;/li&gt;
&lt;li&gt;Pick the type: MySQL/MariaDB → &lt;code&gt;INT UNSIGNED&lt;/code&gt;; PostgreSQL → &lt;code&gt;INET&lt;/code&gt;; IPv6 needs → &lt;code&gt;BINARY(16)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rewrite all queries touching that column to use the integer form — &lt;code&gt;BETWEEN&lt;/code&gt; on the index instead of &lt;code&gt;LIKE '10.0.%'&lt;/code&gt; or string comparison.&lt;/li&gt;
&lt;li&gt;Backfill with staged updates; measure &lt;code&gt;EXPLAIN&lt;/code&gt; before/after on the hottest query.&lt;/li&gt;
&lt;li&gt;Never let an ORM sneak a string back in — cast at the boundary, not in the query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 15 minutes this migration takes will pay for itself the first time a range filter returns the right rows — fast. Integer IP storage is the kind of change that looks like a micro-optimization and behaves like a bugfix: it fixes correctness (string ordering) and performance (index scan) at the same time.&lt;/p&gt;

</description>
      <category>database</category>
      <category>devops</category>
      <category>performance</category>
      <category>python</category>
    </item>
    <item>
      <title>Your Firewall Speaks CIDR, Your Vendor Speaks IP Ranges</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Mon, 05 Oct 2026 14:56:57 +0000</pubDate>
      <link>https://dev.to/yuanke215/your-firewall-speaks-cidr-your-vendor-speaks-ip-ranges-1fm8</link>
      <guid>https://dev.to/yuanke215/your-firewall-speaks-cidr-your-vendor-speaks-ip-ranges-1fm8</guid>
      <description>&lt;p&gt;A vendor sends you the access list to whitelist: "allow &lt;code&gt;203.0.113.16&lt;/code&gt; through &lt;code&gt;203.0.113.79&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;Cool. You paste it into your security group and the console politely refuses. Firewalls, cloud security groups, route tables, and ACLs all want &lt;strong&gt;CIDR blocks&lt;/strong&gt; — a network address and a prefix length — not a start and end address.&lt;/p&gt;

&lt;p&gt;So how many blocks is that range, exactly? And why can't it ever be one?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one range means several blocks
&lt;/h2&gt;

&lt;p&gt;A CIDR block is rigid in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It always spans a &lt;strong&gt;power of two&lt;/strong&gt; number of addresses (&lt;code&gt;/24&lt;/code&gt; = 256, &lt;code&gt;/23&lt;/code&gt; = 512...).&lt;/li&gt;
&lt;li&gt;It always &lt;strong&gt;starts at an aligned boundary&lt;/strong&gt; — a /22 starts at an address whose low 10 bits are zero, a /21 at an address whose low 11 bits are zero, and so on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Real-world ranges don't respect either rule. &lt;code&gt;203.0.113.16 - 203.0.113.79&lt;/code&gt; spans 64 addresses (a power of two, good), but it starts at &lt;code&gt;.16&lt;/code&gt;, which is only aligned for blocks of 16 addresses. A single &lt;code&gt;/26&lt;/code&gt; at &lt;code&gt;203.0.113.0&lt;/code&gt; would cover the whole range — but also 16 addresses you were &lt;em&gt;not&lt;/em&gt; authorized to allow. Over-permissive by 25%.&lt;/p&gt;

&lt;p&gt;The only exact representation is a split. This particular range decomposes into:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CIDR block&lt;/th&gt;
&lt;th&gt;Covers&lt;/th&gt;
&lt;th&gt;Addresses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;203.0.113.16/28&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;.16 – .31&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;203.0.113.32/27&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;.32 – .63&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;203.0.113.64/28&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;.64 – .79&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three blocks, 64 addresses, union exactly equals the range. That's the firewall-safe answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The algorithm: greedily take the biggest legal block
&lt;/h2&gt;

&lt;p&gt;The standard decomposition walks the range from its start, and at each step takes the largest block that satisfies both constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ip_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;int_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;low_bit_align&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Largest power-of-two block size aligned at v.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;//=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;floor_log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;//=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;range_to_cidr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;low_bit_align&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;floor_log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;
        &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;int_to_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pfx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range_to_cidr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ip_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;203.0.113.16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;ip_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;203.0.113.79&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pfx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; addresses)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each iteration is capped by two limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alignment limit&lt;/strong&gt; — how big a block &lt;em&gt;can&lt;/em&gt; start here. &lt;code&gt;low_bit_align&lt;/code&gt; counts the trailing zero bits of the current address: &lt;code&gt;203.0.113.16&lt;/code&gt; ends in four zero bits (16 = &lt;code&gt;10000&lt;/code&gt; in binary's low positions), so at most a 16-address block can start there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Span limit&lt;/strong&gt; — how big a block &lt;em&gt;fits&lt;/em&gt; before the range's end. The largest power of two not exceeding the remaining addresses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take the smaller cap, emit the block, jump past it, repeat. Because each step grabs the biggest legal block, the result is the &lt;strong&gt;minimum&lt;/strong&gt; number of CIDR blocks — no other exact cover uses fewer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The staircase effect
&lt;/h2&gt;

&lt;p&gt;When a range starts and ends on ragged boundaries, the output looks strange at first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.0.0.1  -  10.0.0.254   →

10.0.0.1/32
10.0.0.2/31
10.0.0.4/30
10.0.0.8/29
10.0.0.16/28
10.0.0.32/27
10.0.0.64/26
10.0.0.128/26
10.0.0.192/27
10.0.0.224/28
10.0.0.240/29
10.0.0.248/30
10.0.0.252/31
10.0.0.254/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fourteen blocks for 254 addresses! That's not a bug — it's the shape of an exact cover when you exclude the network address (&lt;code&gt;10.0.0.0&lt;/code&gt;) and the broadcast address (&lt;code&gt;10.0.0.255&lt;/code&gt;) from an otherwise tidy /24. The small blocks nibble the ragged start, big blocks eat the middle, and small blocks finish the ragged tail.&lt;/p&gt;

&lt;p&gt;Compare that with the clean version: &lt;code&gt;10.20.0.0 - 10.23.255.255&lt;/code&gt; decomposes to a single &lt;code&gt;10.20.0.0/14&lt;/code&gt;... if the addresses align. They do here, which is why designing ranges on power-of-two boundaries from day one saves everyone pain later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this conversion actually pays off
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security group and firewall rules&lt;/strong&gt; — the vendor-scope case above. Exact blocks mean you never allow more than you intended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route summarization&lt;/strong&gt; — merging site ranges into the shortest prefix list keeps routing tables small.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud migrations&lt;/strong&gt; — moving &lt;code&gt;10.20.0.0 - 10.23.255.255&lt;/code&gt; into AWS VPC subnets requires an exact decomposition before you can type a single subnet CIDR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IPAM imports&lt;/strong&gt; — spreadsheets record ranges; DHCP scopes, monitoring systems, and ACLs want prefixes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The math is small enough to write yourself (you just saw the whole algorithm), but when you're staring at a migration spreadsheet with forty ranges, an &lt;a href="https://ipcalcplus.com/ip-range-to-cidr.html" rel="noopener noreferrer"&gt;IP range to CIDR converter&lt;/a&gt; that prints the block list with masks and address counts saves an afternoon of manual binary arithmetic — and unlike a firewall, it won't silently truncate your scope if you get an octet wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules of thumb
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;If your range's length is a power of two &lt;strong&gt;and&lt;/strong&gt; the start is aligned, you get exactly one block. Everything else splits.&lt;/li&gt;
&lt;li&gt;Blocks never overlap in a correct decomposition — they tile the range edge to edge.&lt;/li&gt;
&lt;li&gt;The block count explodes only at ragged boundaries. Round your allocations to power-of-two boundaries and future conversions collapse to a single line.&lt;/li&gt;
&lt;li&gt;Always verify the union covers your range &lt;em&gt;exactly&lt;/em&gt; — an extra address in a firewall rule is a security incident waiting for an auditor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next time a vendor sends you a range, you'll know in thirty seconds whether it's one clean block, a three-block staircase, or a fourteen-block monster — and exactly what to paste into that security group.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>cloud</category>
      <category>security</category>
      <category>python</category>
    </item>
    <item>
      <title>The Subnet Overlap Nobody Saw Coming (and the 30-Second Check That Catches It)</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Mon, 05 Oct 2026 14:47:06 +0000</pubDate>
      <link>https://dev.to/yuanke215/the-subnet-overlap-nobody-saw-coming-and-the-30-second-check-that-catches-it-3ill</link>
      <guid>https://dev.to/yuanke215/the-subnet-overlap-nobody-saw-coming-and-the-30-second-check-that-catches-it-3ill</guid>
      <description>&lt;p&gt;Two offices merge. Both teams have been happily using &lt;code&gt;10.34.x.x&lt;/code&gt; for years. The network team stitches the sites together with a new VPN tunnel, updates the firewall, and goes home.&lt;/p&gt;

&lt;p&gt;Three days later the help desk floods with tickets: the build server answers pings but not SSH, a printer works on Tuesdays, and one department swears the CRM "loads eventually, if you refresh five times."&lt;/p&gt;

&lt;p&gt;Nothing is on fire. Everything is &lt;em&gt;intermittent&lt;/em&gt;. Welcome to the worst failure mode in subnetting: &lt;strong&gt;overlapping subnets&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case that started it
&lt;/h2&gt;

&lt;p&gt;Site A was assigned &lt;code&gt;10.34.12.0/23&lt;/code&gt;. That block covers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.34.12.0  -  10.34.13.255   (512 addresses)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Site B, allocated by a different admin in a different year, got &lt;code&gt;10.34.13.0/24&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.34.13.0  -  10.34.13.255   (256 addresses)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On paper both allocations look fine, and neither admin ever saw the other's spreadsheet. But B's entire /24 is &lt;em&gt;inside&lt;/em&gt; A's /23. Every one of B's 256 addresses is claimed twice.&lt;/p&gt;

&lt;p&gt;Once the tunnel comes up, packets for &lt;code&gt;10.34.13.x&lt;/code&gt; have two valid destinations. The routing table picks whichever route was installed last, ARP caches fight over the same targets, and traffic lands on whichever site the router currently prefers. Intermittent, direction-dependent, and maddening to troubleshoot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The math is simpler than the debugging
&lt;/h2&gt;

&lt;p&gt;Two subnets overlap exactly when their address intervals share at least one address. Forget prefix lengths — expand both blocks into a start and end address and compare intervals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;overlap(A, B)  =  start(A) &amp;lt;= end(B)  AND  start(B) &amp;lt;= end(A)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. It works for equal prefixes, different prefixes, a /23 versus a /24, or a single host versus a whole /8.&lt;/p&gt;

&lt;p&gt;In Python, the whole check fits on a napkin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ip_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mask_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;block_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ip_to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;mask_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;overlaps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a_pfx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b_pfx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;block_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a_pfx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;b_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;block_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b_pfx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a_start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;b_end&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;b_start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;a_end&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;overlaps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.34.12.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.34.28.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# False - 3,584 apart, fine
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;overlaps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.34.12.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.34.13.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# True  - the incident above
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the blocks do &lt;em&gt;not&lt;/em&gt; overlap, the same numbers tell you how much room is left between them — useful when you're about to hand out the next slice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three mistakes I keep seeing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Comparing prefixes instead of intervals.&lt;/strong&gt; "It's a /23 and a /24, so they're different" is not a safety check. Nested prefixes overlap by definition. So do &lt;code&gt;10.34.13.0/24&lt;/code&gt; and &lt;code&gt;10.34.13.128/25&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Trusting the network address someone typed.&lt;/strong&gt; &lt;code&gt;10.34.12.7/23&lt;/code&gt; and &lt;code&gt;10.34.12.0/23&lt;/code&gt; are the same block — the network address is where the mask says it is, not where the host octets happen to sit. Always normalize before comparing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Assuming private space is infinite and therefore safe.&lt;/strong&gt; RFC 1918 gives you a lot of room, but mergers are exactly when two orgs discover they both carved up &lt;code&gt;10.x.x.x&lt;/code&gt; differently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Addresses&lt;/th&gt;
&lt;th&gt;Typical owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10.0.0.0/8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10.0.0.0 – 10.255.255.255&lt;/td&gt;
&lt;td&gt;16,777,216&lt;/td&gt;
&lt;td&gt;Large enterprises&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;172.16.0.0/12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;172.16.0.0 – 172.31.255.255&lt;/td&gt;
&lt;td&gt;1,048,576&lt;/td&gt;
&lt;td&gt;Mid-size networks, Docker defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;192.168.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;192.168.0.0 – 192.168.255.255&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;Home and small office&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One more trap: &lt;code&gt;100.64.0.0/10&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; private. RFC 6598 reserved it for carrier-grade NAT, and mobile carriers hand addresses from it to millions of devices. It &lt;em&gt;looks&lt;/em&gt; internal (it isn't globally routed), but it isn't yours to subnet either. If you inherit an address plan using that block, treat it as someone else's shared space, not a free pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-second pre-flight check
&lt;/h2&gt;

&lt;p&gt;Before any allocation goes into the IPAM tool, run the interval check against every existing block in the same region:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Normalize the new block to its true start and end address.&lt;/li&gt;
&lt;li&gt;Compare intervals against all neighbors — different prefix lengths included.&lt;/li&gt;
&lt;li&gt;If anything overlaps, stop and re-plan. If nothing does, note the gap so the next allocation starts cleanly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can do this in a REPL, in a spreadsheet, or in a tool built for exactly this job — I keep a &lt;a href="https://ipcalcplus.com/private-ip-range-calculator.html" rel="noopener noreferrer"&gt;private IP range calculator&lt;/a&gt; bookmarked for the check, because it also tells me which RFC 1918 block (or special range like CGNAT, link-local, or loopback) an address falls into, which catches the third mistake in the same pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why overlap is worse than a full outage
&lt;/h2&gt;

&lt;p&gt;A hard outage gets paged immediately and fixed loudly. An overlap fails &lt;em&gt;quietly&lt;/em&gt;: it usually doesn't break the path the monitoring probes use, so dashboards stay green while a subset of hosts flips between destinations. The failure surface is routing order, ARP timing, and DHCP lease renewals — all nondeterministic from the help desk's point of view.&lt;/p&gt;

&lt;p&gt;That's why prevention is the whole game. The check costs 30 seconds before deployment; the incident costs a week of packet captures after.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>devops</category>
      <category>sysadmin</category>
      <category>cloud</category>
    </item>
    <item>
      <title>401 vs 403 vs 404: The Status Code Mistakes That Break APIs</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Sun, 27 Sep 2026 03:11:16 +0000</pubDate>
      <link>https://dev.to/yuanke215/401-vs-403-vs-404-the-status-code-mistakes-that-break-apis-20a1</link>
      <guid>https://dev.to/yuanke215/401-vs-403-vs-404-the-status-code-mistakes-that-break-apis-20a1</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ipcalcplus.com/http-status-codes-guide.html" rel="noopener noreferrer"&gt;ipcalcplus.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every API review I've ever done contains at least one of these bugs: a login failure that returns 500, a rate limit that returns 400, a "resource not found" that returns 403 for security reasons and confuses every client developer for the next two years.&lt;/p&gt;

&lt;p&gt;Status codes are the API's contract with its clients. Break the contract and your consumers write defensive code around your bugs — code that then breaks when you fix the bug. This is a guide to the codes that actually matter, the distinctions people get wrong, and the two codes almost nobody uses correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-second mental model
&lt;/h2&gt;

&lt;p&gt;First digit = who broke it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2xx&lt;/strong&gt; — it worked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3xx&lt;/strong&gt; — go somewhere else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4xx&lt;/strong&gt; — the &lt;em&gt;client&lt;/em&gt; broke it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5xx&lt;/strong&gt; — the &lt;em&gt;server&lt;/em&gt; broke it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1xx&lt;/strong&gt; — hold on, I'm not done (mostly invisible; the one you'll see is 101 during a WebSocket upgrade)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole taxonomy. Everything interesting is in the distinctions within 4xx and 3xx.&lt;/p&gt;

&lt;h2&gt;
  
  
  401 vs 403: the distinction clients depend on
&lt;/h2&gt;

&lt;p&gt;The most misused pair in REST.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;401 Unauthorized&lt;/strong&gt; actually means &lt;em&gt;unauthenticated&lt;/em&gt;. The server does not know who you are. The client's correct response: acquire credentials (log in, refresh the token, add the API key) and retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;403 Forbidden&lt;/strong&gt; means &lt;em&gt;authenticated but not allowed&lt;/em&gt;. The server knows exactly who you are and is declining to serve you. The client's correct response: give up. Retrying with the same credentials will produce the same answer.&lt;/p&gt;

&lt;p&gt;When you return 403 for missing credentials, clients can't tell "I need to log in" from "I'm not permitted," and their retry logic breaks. The memorable version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;401 = "who are you?"&lt;/li&gt;
&lt;li&gt;403 = "I know who you are, and no."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One deliberate exception: many APIs return 404 for resources the caller isn't allowed to know exist — a private repo is "not found" rather than "forbidden," so the API doesn't leak which IDs are valid. That's a legitimate design choice; document it, because it's the opposite of what clients expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  404 vs 410, and the SEO angle nobody mentions
&lt;/h2&gt;

&lt;p&gt;404 Not Found is the most famous code and the most expensive one to misuse on the web. The nuance: &lt;strong&gt;410 Gone&lt;/strong&gt; means "this used to exist and was deliberately removed." Search engines treat 410 as a stronger signal than 404 — a 404 page may come back, a 410 page won't, so it gets deindexed faster.&lt;/p&gt;

&lt;p&gt;The related rule that preserves years of SEO work: when a URL moves permanently, &lt;strong&gt;301&lt;/strong&gt; (or &lt;strong&gt;308&lt;/strong&gt; for endpoints where the method must not change, like POST endpoints). A redesign that maps old URLs to 404 instead of 301 drops your search traffic within weeks — I've watched it happen, and the fix is always the same retrospective: export the old URL list, redirect everything, verify each target returns 200.&lt;/p&gt;

&lt;p&gt;Within redirects, the pair that matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;301 vs 302&lt;/strong&gt;: permanent vs temporary. 301 transfers ranking to the new URL; 302 does not. Browsers convert POST to GET after a 301.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;307 vs 308&lt;/strong&gt;: same meanings, but method and body are preserved. For APIs, 307/308 are usually the correct choice; for public web pages, 301 remains the standard.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pair everyone underuses: 202 and 204
&lt;/h2&gt;

&lt;p&gt;Two codes that make APIs honest:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;202 Accepted&lt;/strong&gt; — "I received your request and will process it, but it isn't done." If your POST enqueues a job for async processing, 202 is the truthful answer. Returning 200 when the work hasn't happened yet means your client thinks the email was sent when it's actually still in a queue that will retry at 3 AM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;204 No Content&lt;/strong&gt; — "succeeded, and there's nothing to return." The clean response for DELETE, or any operation with no meaningful body. 204 also carries a subtle contract: the response &lt;em&gt;must&lt;/em&gt; have no body. Returning 200 with an empty string is what most APIs do, and it forces clients to handle two shapes for the same outcome.&lt;/p&gt;

&lt;p&gt;The general principle behind both: &lt;strong&gt;return the code that describes what actually happened, not the code that makes the client happy.&lt;/strong&gt; Fake-200 APIs push complexity downstream into consumer code, where it multiplies.&lt;/p&gt;

&lt;h2&gt;
  
  
  4xx codes that are worth their tokens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;429 Too Many Requests&lt;/strong&gt; — you're rate limiting. Include a &lt;code&gt;Retry-After&lt;/code&gt; header; well-behaved clients honor it, and your effective load drops immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;422 Unprocessable Entity&lt;/strong&gt; — syntactically valid JSON, semantically wrong data (email field contains a phone number). Many APIs collapse this into 400, which forces clients to parse the error body to learn what kind of failure it was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;415 Unsupported Media Type&lt;/strong&gt; — you sent XML to a JSON endpoint. Distinct from 400, and trivially cheap to check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;451 Unavailable For Legal Reasons&lt;/strong&gt; — the takedown code, named after Fahrenheit 451. You'll rarely return it, but you should know why your favorite site vanished in some region.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When the server is lying: 502, 503, 504
&lt;/h2&gt;

&lt;p&gt;The 5xx trio behind every "is it just me?" incident:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;502 Bad Gateway&lt;/strong&gt; — your proxy (nginx, the load balancer) reached the backend and got garbage back, or nothing. Usually means the backend crashed or is misconfigured. Read the &lt;em&gt;proxy's&lt;/em&gt; error log, not just the app log.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;503 Service Unavailable&lt;/strong&gt; — deliberately refusing: overloaded, or in maintenance. This is the "back off" signal, and the right response from a circuit breaker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;504 Gateway Timeout&lt;/strong&gt; — the backend is up but too slow. Different fix from 502: this one is about timeouts and slow queries, not crashes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The debugging shortcut: 502/504 mean "the proxy couldn't talk to the app" and 503 means "the app or the operator chose to say no." Three different log files, three different fixes — which is why "the site is down" tickets get resolved so much faster when the first question asked is &lt;em&gt;which exact 5xx&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A debugging workflow that works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Reproduce with curl, capture headers, not just the body&lt;/li&gt;
&lt;li&gt;4xx? It's the request — validate payload, URL, auth header, Content-Type&lt;/li&gt;
&lt;li&gt;5xx? It's the server — application log first, proxy log second&lt;/li&gt;
&lt;li&gt;Read the response &lt;em&gt;headers&lt;/em&gt;: &lt;code&gt;Retry-After&lt;/code&gt;, &lt;code&gt;Location&lt;/code&gt;, &lt;code&gt;WWW-Authenticate&lt;/code&gt;, &lt;code&gt;Allow&lt;/code&gt; usually contain the actual fix&lt;/li&gt;
&lt;li&gt;Fix minimally, redeploy, retest with the exact same request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The habit that pays compound interest: save the full headers of every interesting failure. The status code names the problem class; the headers usually name the problem.&lt;/p&gt;




&lt;p&gt;If you want a searchable list of all this while you work — including the lesser-known codes and which fix goes with which — I keep a free &lt;a href="https://ipcalcplus.com/http-status-codes.html" rel="noopener noreferrer"&gt;HTTP status code reference&lt;/a&gt; and a longer &lt;a href="https://ipcalcplus.com/http-status-codes-guide.html" rel="noopener noreferrer"&gt;1xx-to-5xx guide&lt;/a&gt; at IPCalcPlus. Both run entirely in the browser, no signup. The &lt;a href="https://ipcalcplus.com/port-checker.html" rel="noopener noreferrer"&gt;Port Checker&lt;/a&gt; is next to it for the other half of "is it the network or the app" debugging.&lt;/p&gt;

&lt;p&gt;Which wrong status code do you see most often in the wild? My vote: 500 for expired tokens.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Five Cron Fields, One Trap: The Scheduling Bug Nobody Expects</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Sun, 27 Sep 2026 03:05:27 +0000</pubDate>
      <link>https://dev.to/yuanke215/five-cron-fields-one-trap-the-scheduling-bug-nobody-expects-17i2</link>
      <guid>https://dev.to/yuanke215/five-cron-fields-one-trap-the-scheduling-bug-nobody-expects-17i2</guid>
      <description>&lt;p&gt;Every backend eventually grows a cron job. Backups at 2 AM, digests at 8 AM, health checks every five minutes. And every team eventually hits the same wall: the schedule that fires at a time nobody asked for, in a timezone nobody configured, on a day-of-week nobody intended.&lt;/p&gt;

&lt;p&gt;Cron syntax is five fields. It fits on a sticky note. The bugs do not come from the syntax — they come from the three places where different implementations disagree with each other. This is a field guide to all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five fields, in one breath
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt; ┌───────────── &lt;span class="n"&gt;minute&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;-&lt;span class="m"&gt;59&lt;/span&gt;)
 │ ┌───────────── &lt;span class="n"&gt;hour&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;-&lt;span class="m"&gt;23&lt;/span&gt;)
 │ │ ┌───────────── &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; (&lt;span class="m"&gt;1&lt;/span&gt;-&lt;span class="m"&gt;31&lt;/span&gt;)
 │ │ │ ┌───────────── &lt;span class="n"&gt;month&lt;/span&gt; (&lt;span class="m"&gt;1&lt;/span&gt;-&lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;JAN&lt;/span&gt;-&lt;span class="n"&gt;DEC&lt;/span&gt;)
 │ │ │ │ ┌───────────── &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;-&lt;span class="m"&gt;6&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;SUN&lt;/span&gt;-&lt;span class="n"&gt;SAT&lt;/span&gt;; &lt;span class="m"&gt;7&lt;/span&gt; = &lt;span class="n"&gt;Sunday&lt;/span&gt; &lt;span class="n"&gt;too&lt;/span&gt;)
 * * * * *  &lt;span class="n"&gt;command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four operators cover 99% of real expressions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt; — every value in the field&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;,&lt;/code&gt; — list: &lt;code&gt;6,18&lt;/code&gt; in hours = 6 AM and 6 PM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt; — range: &lt;code&gt;MON-FRI&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; — step: &lt;code&gt;*/15&lt;/code&gt; in minutes = every 15 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One subtlety worth internalizing: &lt;code&gt;*/15&lt;/code&gt; starts at the field's zero (0, 15, 30, 45), but &lt;code&gt;5/15&lt;/code&gt; starts at 5 (5, 20, 35, 50). The start value shifts the whole sequence — this is the difference between "aligned" and "offset" intervals, and it matters when you are spreading load across workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: day-of-month OR day-of-week
&lt;/h2&gt;

&lt;p&gt;Here is the bug I have now seen three times in production, always from a competent engineer, always written down as "cron is weird."&lt;/p&gt;

&lt;p&gt;The expression &lt;code&gt;0 0 1 * 1&lt;/code&gt; looks like it means "midnight on the first of the month, if it's a Monday."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In standard Unix cron, it means "midnight on the first of the month AND midnight on every Monday."&lt;/strong&gt; The relationship is OR, not AND. That job fires ~48 times a year instead of ~1.&lt;/p&gt;

&lt;p&gt;The rule, straight from the Vixie cron man page: when &lt;em&gt;both&lt;/em&gt; the day-of-month and day-of-week fields are restricted (neither is &lt;code&gt;*&lt;/code&gt;), the job runs when &lt;strong&gt;either&lt;/strong&gt; matches. To get true AND logic you need Quartz-style syntax (&lt;code&gt;0 0 1 ? * 1&lt;/code&gt;, where &lt;code&gt;?&lt;/code&gt; means "no specific value") or a guard in the script itself.&lt;/p&gt;

&lt;p&gt;This single rule explains most "my cron job runs at the wrong time" mysteries. If your target platform is Quartz, Spring, or Jenkins, check their docs — most treat the two day fields differently from Unix cron, which is exactly why the bug survives code review: it worked on the engineer's machine, and their machine was running a different scheduler.&lt;/p&gt;

&lt;h2&gt;
  
  
  The L, W, and # extensions (and where they get you fired)
&lt;/h2&gt;

&lt;p&gt;Modern schedulers — Quartz, Spring, AWS EventBridge, Kubernetes — support three extensions beyond classic cron:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;L&lt;/code&gt; — last: &lt;code&gt;0 0 L * *&lt;/code&gt; = last day of the month; &lt;code&gt;0 0 * * 5L&lt;/code&gt; = last Friday&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;W&lt;/code&gt; — nearest weekday: &lt;code&gt;0 0 15W * *&lt;/code&gt; = weekday closest to the 15th&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#&lt;/code&gt; — nth weekday: &lt;code&gt;0 0 * * 1#3&lt;/code&gt; = third Monday&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are genuinely useful for monthly report jobs. They are also &lt;strong&gt;silently invalid in classic Vixie cron&lt;/strong&gt; — the daemon treats them as syntax errors. Copy an &lt;code&gt;L&lt;/code&gt; expression from a Quartz tutorial into &lt;code&gt;/etc/cron.d/&lt;/code&gt; and your job simply never runs. No warning, no email, just silence.&lt;/p&gt;

&lt;p&gt;The portability rule: &lt;strong&gt;know your target before you copy the expression.&lt;/strong&gt; The quick reference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;th&gt;L/W/#&lt;/th&gt;
&lt;th&gt;Timezone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unix crontab&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quartz / Spring&lt;/td&gt;
&lt;td&gt;6-7&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;JVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes CronJob&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;node (or &lt;code&gt;timeZone&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS EventBridge&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;UTC&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;UTC&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Timezone and DST: the silent schedule-shifters
&lt;/h2&gt;

&lt;p&gt;Two production incidents waiting to happen:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. UTC defaults in the cloud.&lt;/strong&gt; "0 9 * * &lt;em&gt;" means 9 AM in *the scheduler's timezone&lt;/em&gt;. On GitHub Actions and AWS EventBridge, that is UTC. Your 9 AM daily report for the New York office goes out at 5 AM local. Nobody notices for a week because 5 AM email is just "early," until someone checks timestamps during a debugging session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Daylight saving transitions.&lt;/strong&gt; A job scheduled inside the spring-forward hour (e.g., 2:30 AM in a US timezone) &lt;strong&gt;does not run at all&lt;/strong&gt; that day — the wall-clock time never happens. During fall-back, a job in the repeated hour runs twice. If "runs exactly once per day" is a billing or audit requirement, that's a defect. The standard fix: schedule critical jobs in UTC, or outside DST transition windows.&lt;/p&gt;

&lt;p&gt;The general principle: for anything with an external SLA (backups, billing, cert renewal), &lt;strong&gt;write the cron expression in UTC and document that it is UTC.&lt;/strong&gt; For anything user-facing, use a scheduler that supports explicit timezones and set it deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six expressions worth memorizing
&lt;/h2&gt;

&lt;p&gt;You will type these for the rest of your career:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0 2 * * *&lt;/code&gt; — nightly at 2 AM (backups, the classic low-traffic window)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*/5 * * * *&lt;/code&gt; — every 5 minutes (health checks, watchdogs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0 9 * * MON-FRI&lt;/code&gt; — weekday mornings (reports, digests)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;15 4 * * 0&lt;/code&gt; — Sunday 4:15 AM (log rotation, cleanup)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0 */2 * * *&lt;/code&gt; — every 2 hours (syncs, cache refresh)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0 0 1 * *&lt;/code&gt; — first of the month (invoices, archives)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the debug checklist when a job "didn't run": (1) is the daemon alive? (2) is the expression valid &lt;em&gt;for this scheduler&lt;/em&gt;? (3) is the timezone what you think it is? (4) did a DST boundary swallow it? (5) check the scheduler's own log, not just your application log. In my experience the answer is (3) about half the time.&lt;/p&gt;




&lt;p&gt;When I'm porting a schedule between platforms I run it through a free &lt;a href="https://ipcalcplus.com/cron-expression-generator.html" rel="noopener noreferrer"&gt;Cron Expression Generator&lt;/a&gt; first — it parses the expression, describes it in plain English, and shows the next 10 run times, so a UTC bug or an OR-logic trap is visible &lt;em&gt;before&lt;/em&gt; it ships. It runs entirely in the browser. There's a longer reference writeup of everything above, including the full platform table, in the &lt;a href="https://ipcalcplus.com/cron-expressions-guide.html" rel="noopener noreferrer"&gt;Cron Expressions Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's the worst scheduling bug you've shipped? Bonus points if the root cause was a timezone.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>backend</category>
    </item>
    <item>
      <title>Stop Guessing Subnet Sizes: A Practical Mental Model for IPv4 Subnetting</title>
      <dc:creator>yuanke215</dc:creator>
      <pubDate>Sun, 27 Sep 2026 03:00:08 +0000</pubDate>
      <link>https://dev.to/yuanke215/stop-guessing-subnet-sizes-a-practical-mental-model-for-ipv4-subnetting-5f9n</link>
      <guid>https://dev.to/yuanke215/stop-guessing-subnet-sizes-a-practical-mental-model-for-ipv4-subnetting-5f9n</guid>
      <description>&lt;p&gt;Most people learn subnetting as a pile of formulas: count the bits, subtract two, memorize the table. That works for exams, but it falls apart the moment you are standing in front of a real network with an IP like &lt;code&gt;10.0.5.130/27&lt;/code&gt; and someone asks "what network is this on?"&lt;/p&gt;

&lt;p&gt;This article is the version I wish someone had given me: a single mental model that turns every subnetting question into arithmetic you can do in your head, plus the three shortcuts that make it fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one idea that makes subnetting click
&lt;/h2&gt;

&lt;p&gt;An IPv4 address is 32 bits. A prefix like &lt;code&gt;/26&lt;/code&gt; means: &lt;strong&gt;the first 26 bits are the network, the last 6 bits are the host.&lt;/strong&gt; That's it. Everything else follows from this sentence.&lt;/p&gt;

&lt;p&gt;The consequences of those 6 host bits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total addresses in the subnet: &lt;code&gt;2^6 = 64&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Usable hosts: &lt;code&gt;64 - 2 = 62&lt;/code&gt; (the first address is the network ID, the last is the broadcast)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice you never needed the subnet mask &lt;code&gt;255.255.255.192&lt;/code&gt;. The prefix and the mask are the same statement in two languages — but the prefix form is the one that keeps the math obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  The block size shortcut (replaces binary math)
&lt;/h2&gt;

&lt;p&gt;Here is the shortcut that removes binary from your daily life:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Block size = 256 minus the last non-255 octet of the mask.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/26&lt;/code&gt; → mask ends in &lt;code&gt;.192&lt;/code&gt; → block = &lt;code&gt;256 - 192 = 64&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/27&lt;/code&gt; → &lt;code&gt;.224&lt;/code&gt; → block = 32&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/28&lt;/code&gt; → &lt;code&gt;.240&lt;/code&gt; → block = 16&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have the block size, &lt;strong&gt;subnets can only start at multiples of that block&lt;/strong&gt;: 0, 64, 128, 192 for a /26. No exceptions. This is the single most useful fact in practical subnetting, because it means you can validate anyone's network design in seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Worked example: which subnet is 10.0.5.130/27 on?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Block size = 32&lt;/li&gt;
&lt;li&gt;List multiples: 0, 32, 64, 96, 128, 160...&lt;/li&gt;
&lt;li&gt;130 falls between 128 and 160 → network is &lt;code&gt;10.0.5.128/27&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Usable range: &lt;code&gt;.129&lt;/code&gt; to &lt;code&gt;.158&lt;/code&gt;, broadcast &lt;code&gt;.159&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You just did the entire subnetting calculation — network address, usable range, broadcast — with nothing but subtraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sizing: the question people actually get wrong
&lt;/h2&gt;

&lt;p&gt;The exam question "how many hosts in a /28?" is easy. The real question is "I have 28 devices — which prefix do I pick?" And here people consistently fail in the same direction: &lt;strong&gt;too tight.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;28 devices in a &lt;code&gt;/27&lt;/code&gt; (30 usable) = one printer installation from an outage&lt;/li&gt;
&lt;li&gt;The rule I use: &lt;strong&gt;current need + 30-50% headroom, then round up to the next prefix&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So 28 devices → aim for ~40 → &lt;code&gt;/27&lt;/code&gt; gives 30, not enough → &lt;code&gt;/26&lt;/code&gt; gives 62, take it. Wasted space is cheap. Re-numbering a live network is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three mistakes that cause real outages
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Misaligned boundaries.&lt;/strong&gt; Someone configures &lt;code&gt;192.168.1.50/26&lt;/code&gt; as a "new subnet." The mask zeroes the lower 6 bits, so the actual network is &lt;code&gt;192.168.1.0/26&lt;/code&gt; — the same subnet as the engineering VLAN. The config "looks valid" but silently overlaps. Rule: every network address must be a multiple of the block size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Mask mismatch after migration.&lt;/strong&gt; You re-subnet a segment from /24 to /26, but one server keeps the old /24 mask. It ARPs for addresses outside its real subnet, fails, and the ticket says "network is slow" — because the truth (half the hosts are unreachable) is invisible to the application team. Change masks in a maintenance window, all devices, all at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Forgetting the two reserved addresses.&lt;/strong&gt; &lt;code&gt;.0&lt;/code&gt; and &lt;code&gt;.255&lt;/code&gt; (or block edges on other prefixes) are not usable. A device handed a network or broadcast address fails in ways that depend on the OS — which means the symptom looks random.&lt;/p&gt;

&lt;h2&gt;
  
  
  When unequal subnets are the right answer
&lt;/h2&gt;

&lt;p&gt;Equal subnets are a training exercise. Real networks have one 60-person department and three 8-person teams, and giving everyone a /26 wastes three quarters of your address space. The professional answer is &lt;strong&gt;VLSM (Variable Length Subnet Masking)&lt;/strong&gt;: allocate the biggest subnet first, then fit the smaller ones into what remains.&lt;/p&gt;

&lt;p&gt;For a /24 with a 50-host engineering team, a 30-device guest network, and two small teams:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Network&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Engineering (50)&lt;/td&gt;
&lt;td&gt;/26&lt;/td&gt;
&lt;td&gt;&lt;code&gt;192.168.1.0/26&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guest (30)&lt;/td&gt;
&lt;td&gt;/27&lt;/td&gt;
&lt;td&gt;&lt;code&gt;192.168.1.64/27&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team A (12)&lt;/td&gt;
&lt;td&gt;/28&lt;/td&gt;
&lt;td&gt;&lt;code&gt;192.168.1.96/28&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team B (8)&lt;/td&gt;
&lt;td&gt;/28&lt;/td&gt;
&lt;td&gt;&lt;code&gt;192.168.1.112/28&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything fits in the bottom half, and the entire top half stays free for growth. Start big and work down — never the reverse, or you fragment the range.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity-check any design in 60 seconds
&lt;/h2&gt;

&lt;p&gt;Before you touch a router, verify three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Alignment&lt;/strong&gt; — every network address is a multiple of its block size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No overlaps&lt;/strong&gt; — sort subnets by network address; each broadcast must be below the next network address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacity&lt;/strong&gt; — usable hosts ≥ current devices × 1.3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all three pass, the design is sound. If any fails, you have just prevented an outage that would have been very hard to diagnose later — subnetting bugs don't show up as "subnetting bugs," they show up as "the network is weird."&lt;/p&gt;




&lt;p&gt;If you want to verify any of these calculations without doing them by hand — or you inherited a network and want to reverse-engineer which subnet an address belongs to — I maintain a free &lt;a href="https://ipcalcplus.com/subnet-calculator.html" rel="noopener noreferrer"&gt;Subnet Calculator&lt;/a&gt; that runs entirely in your browser (no signup, nothing leaves your device). There is also a &lt;a href="https://ipcalcplus.com/cidr-converter.html" rel="noopener noreferrer"&gt;CIDR converter&lt;/a&gt; for jumping between prefix and range notation, and a &lt;a href="https://ipcalcplus.com/subnet-mask-cheat-sheet.html" rel="noopener noreferrer"&gt;subnet mask cheat sheet&lt;/a&gt; worth pinning next to your terminal.&lt;/p&gt;

&lt;p&gt;What's your subnetting horror story? The misaligned-boundary one in the comments section is guaranteed to be good.&lt;/p&gt;

</description>
      <category>sysadmin</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
