How Swatter Decides Who to Block — the Scoring Engine, Explained
By Josh Comstock · Updated June 2026 · ~8 min read
An abuse blocker lives or dies on one thing — the quality of its decisions. Block too eagerly and you firewall your own paying customers; block too cautiously and the flood walks right through. Most "ban the noisy IP" tools pick a side of that trade-off and just live with the other. Swatter tries to win both ends at once, and the way it scores an IP is where that happens. So this is a walk through how a request in your access log becomes — or doesn't become — a firewall block.
The Job: Turn Behavior Into a Number
Every few minutes Swatter reads your web-server logs and, over a sliding time window, builds a profile of every IP that hit you. Then it scores that IP from 0 to 100 — one number that says, in effect, "how much does this look like abuse?" Everything downstream — whether to act, how hard, and on which firewall plane — keys off that score and the evidence behind it.
The score is a weighted blend of independent behavioral signals. No single one is the whole story — but stack them together and you get a picture that's hard to fake:
| Signal | What it catches |
|---|---|
| Request rate | Hammering and floods |
| Error ratio (4xx/5xx) | Scanners generate errors — real visitors mostly don't |
| Error burst (403/404/444) | Brute-force and path fuzzing |
| URL fanout | Path enumeration — one IP touching many distinct URLs fast |
| Bad-path hits | /.env, /.git, wp-login.php, xmlrpc.php, /cgi-bin/, shell drops, … |
| User-agent | sqlmap, nikto, zgrab, empty UAs |
| POST flood | Login, xmlrpc, and comment spam |
| No-vhost / raw-IP | Only attackers hit you by bare IP with no Host |
| Reputation (optional) | AbuseIPDB, Spamhaus, IPsum and friends — past abuse from this IP |
A normal human browsing your site trips almost none of these. They fetch a handful of real pages, mostly get 200s, send a User-Agent a browser actually uses, and show up through a normal Host header. The whole thing is built so ordinary use sits near zero and abuse climbs.
Why a Blended Score Alone Isn't Enough
Here's the subtle failure mode that sinks naïve scoring. Average a whole pile of signals together and a focused attack gets diluted into invisibility. Picture a credential brute-force against wp-login.php — a steady stream of POSTs to one URL. That lights up the error-burst and POST-flood signals brightly, but it barely touches request-rate (it's patient), URL-fanout (it hits one path), bad-paths (login is a normal path), or user-agent (it forges a browser). Average all nine together and a genuinely dangerous attack scores a mild, sub-threshold "meh." The averaging itself hid it.
So Swatter pairs the blended score with decisive floors. A handful of behaviors are each, on their own, enough evidence to act — regardless of what the blend says:
- A
/.envor/.gitprobe — there's no innocent reason to ask for your secrets file. - A sustained request flood.
- A broad scanner profile (the fanout-plus-errors fingerprint).
- Repeated hits on a sensitive endpoint — the brute-force case above.
Any one of these sets a floor under the IP's score, high enough to trigger action on its own. The blend still catches the diffuse, many-signals-a-little attackers; the floors make sure the sharp, one-signal-a-lot attackers can't slip through the averaging. You catch both shapes of attack without cranking the overall threshold down so far that real traffic starts tripping it.
Why It Almost Never Blocks a Real Visitor
The same design that catches focused attacks is what keeps Swatter off your customers. Two principles do most of the work here.
Brute-Force Decisions Require Failure Evidence
This is the big one. Visiting a sensitive path isn't a crime. A site owner opening wp-login.php, signing in, and working in wp-admin throws off a stream of successful requests — and successful requests aren't evidence of brute force. The brute-force floors look for one thing specifically: failure. Error responses and POST floods, the signature of guessing that isn't working. So the legitimate admin doing real work can't trip the very rule that catches the attacker guessing their password. Same URL, different log signature — and Swatter scores the difference, not the URL.
A TTL Ladder, Not a Hair-Trigger Permaban
Even once an IP earns action, the first consequence is gentle and reversible. A first offense earns a temporary block on a rising TTL ladder — minutes, then longer if it comes back. Permanent bans are earned, and only by repeat offenses across days. That ordering matters on shared and carrier-grade-NAT addresses, where one weird burst might come from an IP that thousands of real people also sit behind — a single bad window costs that IP a short timeout, never a permanent blackhole. Swatter tracks the low-and-slow offenders too — the probers who deliberately linger just under the threshold across hours — so patience doesn't buy an attacker immunity. But a one-off spike from a real network doesn't buy a permanent ban either.
Every Decision Shows Its Work
A score you can't inspect is a score you can't trust. So every decision Swatter makes gets written down — with the evidence — to /var/log/swatter/decisions.jsonl, and you can ask about any IP directly:
$ swatter why 45.146.165.10
# → the signals that fired, the score, the decisive floor (if any),
# the plane it was sent to, and the block's TTL
That audit trail is also why Swatter ships report-only. Point it at your logs, let it watch for a week, and read what it would have done before you ever let it near the firewall. You calibrate against your own real traffic — not someone else's idea of "normal" — and only flip on enforcement once the decisions look right to you. And when it does act, the nightly report rolls the same evidence up plane by plane, so you can see the whole week's decisions at a glance.
It's All Tunable
The defaults are conservative on purpose, but nothing's hard-coded into a black box. Signal weights, thresholds, and the bad-path table all live in plain config — /etc/swatter/swatter.conf and /etc/swatter/badpaths.conf — so you can dial sensitivity to your own site, add the paths your stack treats as sensitive, and guarantee specific IPs with swatter allow. The scoring engine is opinionated, not rigid.
Why This Matters
Here's why any of this is worth the care. On a real server the cost of a wrong decision is asymmetric and brutal. A missed attacker spikes your load; a wrongly-blocked customer is a support ticket, a lost sale, or a locked-out client — and a blocker that cries wolf gets switched off, which is the worst outcome of all. So Swatter scores behavior instead of addresses, demands failure evidence before it treats anyone as an attacker, and escalates gradually with a full audit trail — all so it can be a blocker you actually leave on. That's the whole point of getting the scoring right.
Related: How attackers bypass Cloudflare and hit your origin directly · How to block attackers on a cPanel server behind Cloudflare · CrowdSec vs Swatter vs fail2ban.