My production box was running at 91% CPU, with 490 connections and 22 slow requests in the previous 60 seconds, while the site crawled for real paying customers. I had been treating individual IPs as the attacker in the application layer, but five /19 allocations made the operator, not the address, the useful unit. The planned edge migration was the durable answer, but it was days to weeks away. I needed a change that stopped the pressure immediately.

The cause was a proxy swarm from one commercial proxy operator. It fanned roughly 40,000 IPs across five separate /19 allocations at one public roster endpoint, enumerating team usernames one by one with spoofed browser-version strings and no referrer.

So I wrote the temporary measure. One host firewall rule per registry-listed range:

Terminal window
<host-firewall-command> -Direction Inbound -Action Block `
-DisplayName "Block-Allocation1-20260526" `
-RemoteAddress <cidr-allocation-1>

Five rules, one per allocation, each a /19 block from the operator’s published range. I authored the command as a script file, copied it to the box, and ran it with the available local policy bypass rather than fighting nested quoting in an inline remote command. Complex remote commands belong in a file, not in a one-liner retyped four times.

The measured result was immediate. CPU dropped from 91% to 13%. Connections went from 490 to 146. The slow-60-second-request count went from 22 to 1 in about 15 seconds. Roughly 40,000 IPs were blocked. No customer support ticket or complaint traced back to the block in the days that followed, which was the check available at the time, not a guarantee that every blocked address was illegitimate traffic.

A 15-second block that restores a bleeding site is correct triage, not a confession. The durable change gets built carefully on its own clock. The bleeding stops now.

Why a temporary measure became an insult

The disdain for firewall blocking comes from a real failure mode. People reach for an IP block, feel done, and never build the durable thing. The block becomes the permanent answer, and three months later there are 200 hand-rolled firewall rules nobody can reason about. The next swarm then arrives on new addresses.

That is a real anti-pattern. The mistake is concluding that the block itself was wrong. The block did its job. It stopped the pain in 15 seconds. The wrong part is treating it as the finish line instead of the first move.

Temporary mitigation and proper fix are not competitors for the same slot. They live at different layers and run on different clocks. The mitigation runs on the clock of the site being down now. The proper fix runs on the clock of building it carefully enough that it does not break something else. Refusing the block until the edge is ready leaves the site down while architecture happens under pressure.

The honesty caveat

The block only addresses the operator I found that day. Another operator or a different netblock can produce the same pattern later. I will have to make this call again. That recurrence is not evidence that the block was wrong. It is the reason the edge fix must exist.

I made the original model explicit when I chose the rule. I had been treating individual IPs as the attacker in the application layer, but the operator was the durable identity across the five /19 allocations. A per-IP response would keep me one step behind the swarm. The range rules were the reversal, not a claim that the edge migration no longer mattered.

This is the part people skip, and it is the part that makes whack-a-mole legitimate instead of lazy. The proper fix has to be recorded as real work, not left as the feeling that the dashboard went green. The honest version of this strategy has three parts every time:

  1. Ship the mitigation, stop the bleeding, and measure that it worked.
  2. Record the proper fix as owned and scheduled work.
  3. Feed the incident record into both layers so each block teaches the system.

Skip the second part and the temporary rule becomes the permanent answer. Skip the third part and every incident starts from zero.

Make each incident upgrade both layers

The difference between treading water and making progress is that incident information flows both directions.

When I blocked the proxy operator, I did not just write five firewall rules and walk away. I recorded the registry maintainer value in a reference note as the recognition key. Scrapers rotate IPs constantly. They rarely change operators. The next swarm that resolves to the same maintainer is a 30-second recognition instead of a fresh hour of registry lookup work. The operator is the durable identity. The IP is disposable.

The same record feeds the other defensive layers. The edge migration gets a real world signature to harden against. The application-level range list gets a recorded task to ingest the operator’s ranges so that layer can recognize them too. One incident leaves three layers smarter: the host firewall has the rule now, the application layer can flag the range, and the edge receives the fingerprint to challenge.

The old reactive model was per IP at the application layer. It was always one step behind because it reasoned about individual addresses. The model that scales is operator level, blocking allocations as units and recording the operator so recognition compounds. Each incident becomes a deposit instead of a fire fought from scratch.

Whack-a-mole gets a bad name because people stop at the whack. The fifth /19 firewall rule carried the same operator record as the first, so the next rotation begins with a 30-second recognition instead of individual-address triage.