Fail2Ban did its job, but there were situations where the result didn't convince me. Sometimes it blocked what it shouldn't have, and other times it didn't seem to catch what actually worried me.
I didn't want to add yet another security tool to the pile. I wanted to solve something that, from my point of view, was missing: that the server could remember an IP's behavior and make decisions taking its history into account, not just the last event recorded.
Out of that idea ARE was born — Abuse Reputation Engine.
🇪🇸 Versión en español en este artículo: https://dev.to/hernan_valero/le-di-memoria-a-fail2ban-la-historia-de-are-y-la-noche-en-que-termine-baneandome-a-mi-mismo-5ekk
If you'd rather watch than read: 60–90s of the live escalation (WATCH → FILTER → TEMP_BAN):
I didn't want to build another Fail2Ban
From the start I was clear on one thing: I didn't want to reinvent Fail2Ban or write another detector that did practically the same thing.
Fail2Ban already served a useful purpose. It parsed logs, detected patterns of suspicious activity, and could apply its own sanctions. What I wanted was to make use of the detections and reports from the tools already running on the server, not to duplicate their work.
The idea behind ARE was to gather those signals into a reputation history, relate them to their category and severity, and let the engine evaluate the whole before deciding how to act.
Fail2Ban and the other sensors provide detections; ARE adds context, maintains the reputation, and makes decisions according to its own policies.
It doesn't mean Fail2Ban stops applying its rules, or that every tool has exactly the same function. It means I wanted an additional layer able to evaluate an IP's accumulated behavior, instead of relying only on decisions made in isolation.
An isolated detection doesn't always tell the whole story. But when it's combined with other events and with what that address has been doing over time, it can take on a different meaning.
I didn't need another detector competing with the ones I already had. I needed a layer that could make use of their results, relate them, and make decisions from a broader perspective.
The problem I wanted to solve
The security tools we use every day have a specific function. Fail2Ban parses events and applies sanctions according to the configured rules. ModSecurity inspects HTTP requests and can block the ones that match certain rules. A firewall controls traffic according to its policies.
I have nothing against those tools. On the contrary, they're part of a good defense. The problem appears when we try to interpret a behavior that doesn't fit easily into an isolated rule.
A failed access attempt can be a legitimate mistake. A strange request can be a scanner. A sequence of attempts spread out over time can reveal something more concerning than any of those events on its own.
How do you tell them apart?
Two scenarios worried me. On one hand, someone who spreads their attempts out to stay below certain thresholds. On the other, a legitimate user who ends up blocked because an unstable connection or a sequence of normal requests looks too much like abuse.
I wasn't trying to solve every security problem with a single tool. I wanted to add a piece that would help interpret context.
The idea was simple: that an IP wouldn't start from zero every time it generated an event.
That's how ARE started to take shape.
ARE: reputation instead of isolated reactions
From the start I was clear that I didn't want to introduce unnecessarily complex infrastructure. The idea was to build an engine that could run on a Linux server, leaning on well-known tools and without depending on an external service.
Bash, SQLite, IPSet, and systemd. Simple components, accessible and relatively easy to inspect.
I didn't need another agent written in a trendy language, or a platform that forced me to maintain extra infrastructure. I needed the decision logic to be understandable, and to be able to know why the system made a particular decision.
ARE's core revolves around a few principles.
Not all events mean the same thing
One of the problems I wanted to avoid was treating every suspicious event as if it had the same importance.
Detecting port reconnaissance is not the same as recording an exploitation attempt. Nor is a protocol anomaly the same as an attempt to obtain credentials.
That's why ARE organizes events into nine categories: RECON, EXPLOIT, CREDENTIAL, PROTOCOL, BOT, ANOMALY, MALWARE, DOS, and SOCIAL.
Each category contributes to the reputation according to its own score. This way, routine scanning activity doesn't necessarily weigh the same as an exploitation signal.
I cared about keeping that distinction. If everything ends up turned into a single number with no context, you lose information that can be important when deciding what to do.
Time matters too
Reputation can't be completely static. An IP address can change hands, belong to a shared connection, or be reused. Keeping a sanction indefinitely without considering the passage of time can end up harming someone who has nothing to do with the original events.
But I also didn't want an IP to recover its reputation from one moment to the next simply because it stopped generating activity for a few minutes.
ARE applies a gradual loss of weight (decay) to the reputation. The history is kept, while its influence decreases over time.
The intent is to strike a balance: to remember enough to detect persistence, without turning a past behavior into an eternal sentence.
Observe first, restrict later
Another design decision was to establish a staggered response:
ALLOW → WATCH → FILTER → TEMP_BAN → BANNED
Not every suspicious IP has to end up banned. The system can go from allowing traffic to watching it, filtering it, and applying more severe restrictions as the reputation grows.
Persistence matters, but so does the severity of what's observed. The sequence represents the decision states defined by ARE; it doesn't mean that any given IP has to go through every stage in that order.
Reputation doesn't reset because a jail releases an IP
This is an important part of the original idea.
Fail2Ban can lift a ban when a jail's policy is met. That means that specific sanction ended; it doesn't by itself prove that the IP's whole history has stopped being relevant.
ARE is meant to keep and evaluate its own reputation. A jail release shouldn't automatically be confused with a global rehabilitation of the address: the engine should weigh the whole set of signals it knows about and apply its own policy.
In my design, this separation of responsibilities matters a lot. The sensors detect, and the existing tools can act within their rules; ARE provides a broader evaluation of the history. The exact way blocks and releases are coordinated depends on the configured integration, and it's an aspect that has to be checked in each deployment.
ARE also allows for sharing reputation between your own servers, using categories and scores as selection criteria, without depending on a third-party platform.
The goal is not to replace everything that already exists. It's to add a layer of context and decision on top of the tools that already do the detection.
The test I decided to run on my own VPS
And one night I decided to put it to the test in a fairly direct way: trying to get my own server to defend itself against me. Designing a system on paper is one thing; checking how it behaves when it receives real activity is another.
ARE was already running on my production VPS. So I decided to run a controlled test from an IP of my own, without involving any third parties and using only non-destructive requests.
There's an important detail: the server wasn't protected by a single barrier.
At the edge I had Cloudflare, which presents challenges to certain kinds of traffic. Behind that, ModSecurity, among other tools, inspected the requests that reached the server. Finally, at the origin was ARE, working with the events its sensors recorded.
To evaluate ARE I needed my test traffic to reach the origin. So I used a controlled exception for my own IP at the perimeter layer.
Once the scenario was set up, I tried the usual reconnaissance and exploitation patterns: SQL injection attempts, local file inclusion (LFI), path traversal, XSS, and command injection. All of it non-destructive, without trying to modify data or compromise information.
It was my server, yes, but it was also in production and had real customers. There was no sense in turning a security test into an incident I had caused myself.
ModSecurity started responding with HTTP 403 codes to the signatures it recognized.
So far, everything was within expectations.
Then I went to check what ARE had recorded.
The numbers, and the moment I thought something was failing
In the first round, my IP's reputation looked like this:
Exploit 9 · Credential 2 · Anomaly 7 Total 18 · Risk: LOW · Decision: WATCH My first reaction was to look at the result from the other side: the browser was still receiving HTTP 200 responses on some requests.
My initial interpretation was that ARE wasn't reacting.
But I had made a fairly common mistake when you're testing your own system: I was expecting an immediate response from a process that works in cycles.
The sensors record the events and ARE processes them according to its evaluation cycle. If I check the state before that cycle finishes, I may be seeing a situation that doesn't yet reflect all the activity I just generated.
An HTTP 200 code doesn't prove, on its own, that a request succeeded from an attack's point of view either. It indicates that the server responded with that code; to interpret the result you have to look at what it responded and what happened next.
I waited, checked the state again, and launched a second, more intense round.
Minutes later, the numbers had changed:
Exploit 12 · Anomaly 13 · Total 27 · Risk: MEDIUM The decision timeline reflected the progression:
WATCH (18, LOW) ↓ FILTER (27, MEDIUM) ↓ BAN The IP ended up blocked.
And that IP was my own home connection.
I had banned myself.
I had to recover access through my VPS's out-of-band console. That's exactly what that kind of access is for: being able to administer a machine when the usual connection paths are no longer available.
It wasn't the most comfortable outcome of the night, but it was one of the most useful checks I could run. The blocking policy hadn't stopped at a figure or an alert: it had produced a real consequence.
What I learned from the test
I'm not going to say that a test like this proves a reputation system is perfectly calibrated. That would be overstating what I actually verified.
What I could observe was how ARE accumulated the signals, how the evaluation changed after a new round of activity, and how it ended up applying an effective restriction.
It also left me with a few lessons.
First: you have to tell a failure apart from a delay.
My initial impression that ARE wasn't reacting got ahead of the available data. The processing time mattered. Before changing the engine's logic, I had to check whether the evaluation cycle had finished.
Second: adding events doesn't necessarily mean adding risk indiscriminately.
Basic reconnaissance shouldn't be treated the same as a sequence of exploitation signals. If we raise the weight of exploratory activity too much, we can end up blocking legitimate traffic. If we lower it too much, we can miss useful signals. Calibration requires observing results and reviewing the thresholds, not just declaring that they work.
Third: a tool can do its job well without having all the information.
ModSecurity blocked the requests it recognized. ARE evaluated the events it received and ended up making its own decision. They're distinct and complementary functions.
Layered security isn't about installing several tools that do exactly the same thing, but about combining mechanisms that cover different parts of the problem.
And fourth: a real block is worth more than a nice line in a log.
Seeing a score go up is useful. Confirming that the policy actually gets applied is useful too. But both have to be accompanied by further testing: false positives, access recovery, persistence of sanctions, and behavior with shared or reused IPs.
That the system blocked me during this test is a sign that the response mechanism worked in that scenario. It's not a guarantee that it will always get it right.
Where ARE is today
ARE is in production, on its 2.8.x branch, and some colleagues are using it too.
The project has documentation on its architecture, its design, and the ban lifecycle. Its goal is still the same one that led me to write the first lines: to add a persistent reputation layer to server security, using the signals the existing tools already produce.
I don't present it as a replacement for Fail2Ban or CrowdSec. Those are projects with their own goals and mechanisms. ARE aims to cover a different space: evaluating an IP's accumulated behavior, keeping reputation by category, and using that information to decide when to watch, filter, or block.
All of it with Bash, SQLite, and tools an administrator can inspect without having to deploy additional infrastructure.
Is it the definitive solution? No. There are still scoring, decay, and response decisions that need to be validated in different scenarios. And a tool that intervenes directly in access to a server always needs prudent policies and a recovery path.
But the fact that I can run it in production, observe its behavior, and test it in a controlled way lets me move forward on something more than an idea.
Why I'm sharing this
ARE was born because there was something about the way bans were handled that didn't convince me. I didn't want to pile up tools or repeat the work the ones I had were already doing. I wanted detections to be able to turn into decisions based on a history.
I wanted an IP to have context. For an event not to always be interpreted in isolation. For the system to be able to tell a one-off signal from a repeating behavior, without turning every anomaly into a sentence.
That night I ended up recovering access to my VPS through the out-of-band console because ARE had blocked my own IP. It's not definitive proof that the model is correct, but it is a real experience that let me check how the system responded.
Now I'm interested in contrasting that approach with other administrators and developers. Not because I think ARE already has all the answers, but because I want to find out where it works well, where it can go wrong, and what I should improve.
If you work with Linux servers, intrusion detection systems, or IP reputation mechanisms, I'd like to hear your opinion on the per-category scoring model, reputation decay, and the staggered ban policy.
What situations would worry you that this approach might miss? Where do you see the greatest risk of false positives? How would you handle IP address reuse and reputation shared between servers?
The code is available under the GPL-3.0 license:
ARE repository: https://github.com/hvalero2001-commits/ARE
Any well-founded technical criticism is welcome. I want to keep testing ARE in real scenarios, review what happens, and fix whatever needs fixing.