·9 min read·Updated Nov 5, 2026

fail2ban vs CrowdSec on a VPS: Which One Actually Blocks Brute Force?

A practical comparison of fail2ban and CrowdSec on a Linux VPS: how each detects attacks, config that works, the privacy trade-off, and what neither tool stops.

Both tools do the same core job: read logs, spot repeated failures, and tell your firewall to drop the source IP. The practical difference is scope. fail2ban is a self-contained log parser with zero external dependencies — good when you want one small server to defend itself and nothing to phone home. CrowdSec adds a community blocklist and a more flexible parsing engine, at the cost of a daemon that talks to an external API by default. Neither one is what keeps an attacker out; your SSH configuration and firewall are. These tools just reduce the noise and buy you time.

If you run a privacy-focused or no-KYC VPS, there's a second consideration most comparisons skip: both tools need logs that contain real client IPs, which puts them in direct tension with log minimization. That's worth planning before you install either.

Quick verdict

  • Pick fail2ban if you run one or two servers, want no outbound connections from the security tooling, and mostly care about SSH and maybe a web login endpoint. It's in every distro's repos, it's boring, and boring is good.
  • Pick CrowdSec if you run several services (nginx, mail, a panel, an API), want pre-written detection scenarios instead of hand-written regex, and want to pre-emptively block IPs already seen attacking other people's servers.
  • Run neither and lose nothing if SSH is keys-only, bound behind a VPN, and you have no public login forms. In that case, bans just make your logs shorter — which is still a legitimate reason to install one.

How each tool actually detects an attack

fail2ban works on filters and jails. A filter is a set of regex patterns matched against a log source (a file, or the systemd journal). A jail ties a filter to a time window, a failure threshold, and a ban action. When sshd logs five failed authentications from one address in ten minutes, fail2ban calls an action — typically inserting the address into an nftables or iptables set — and removes it when the ban expires.

It is entirely local. Nothing leaves the machine. The state lives in a SQLite database in /var/lib/fail2ban/, which is also how bans survive a restart.

CrowdSec splits the job into three pieces: parsers turn raw log lines into structured events, scenarios describe attack patterns (HTTP brute force, path traversal probing, SSH bruteforce, and so on), and bouncers are separate processes that enforce decisions. The firewall bouncer maintains nftables sets; a nginx bouncer can serve a block page instead. Because enforcement is decoupled, you can swap or add enforcement points without touching detection.

CrowdSec's distinguishing feature is the central API: by default the agent reports signals about detected attacks (the attacker's IP, the scenario triggered, a timestamp) and in exchange pulls a community blocklist of addresses other participants have flagged. That blocklist is genuinely useful — most SSH and web scanning traffic comes from hosts that have already hammered someone else — but it is an outbound relationship you should make a conscious decision about.

A fail2ban baseline that doesn't lock you out

On Debian or Ubuntu, never edit jail.conf. Create /etc/fail2ban/jail.local:

[DEFAULT]
backend  = systemd
banaction = nftables-multiport
bantime  = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 10.8.0.0/24 203.0.113.7

[sshd]
enabled = true
mode    = aggressive
port    = 22

Three details matter more than the rest:

  • backend = systemd reads the journal instead of /var/log/auth.log. On modern systemd distros this is more reliable, and it keeps working if you've made journald volatile.
  • banaction = nftables-multiport matters if your firewall is nftables. Mixing fail2ban's iptables actions with an nftables ruleset produces bans that exist but never match traffic. If you built your ruleset from an nftables firewall baseline, make sure fail2ban's chain priority sits ahead of your accept rules — check with nft list ruleset | grep -A5 f2b.
  • ignoreip should contain your VPN subnet and, if you have one, a static admin IP. Getting banned from your own server at 2am is the single most common fail2ban incident.

Verify, don't assume:

fail2ban-client status sshd
journalctl -u fail2ban -n 50 --no-pager

If Currently failed: 0 never moves despite obvious attacks in the journal, your filter isn't matching — test the regex with fail2ban-regex.

For IPv6, remember that an attacker with a /64 has effectively unlimited addresses. Banning single IPv6 addresses is close to pointless; if you're getting IPv6 brute force, ban the /64 or restrict the service to IPv4 and your VPN.

CrowdSec in practice, and the privacy decision

Installation pulls the agent plus a firewall bouncer:

# after adding the CrowdSec repo per upstream docs
apt install crowdsec crowdsec-firewall-bouncer-nftables
cscli collections install crowdsecurity/nginx
cscli metrics
cscli decisions list

cscli metrics tells you whether your log sources are actually being read — the most common misconfiguration is an acquisition file pointing at log paths that don't exist on your distro.

The part to decide deliberately: CrowdSec enrolls with the central API to fetch the community blocklist and, in doing so, sends signals about attacks it detects. Those signals describe attackers, not your users or your content, but they do confirm that a server at your IP exists, runs CrowdSec, and sees traffic at certain times. For most people that's an acceptable trade. If it isn't for your threat model, you can run the agent in a local-only mode without enrollment — you keep the detection engine and lose the shared blocklist. Read the current upstream docs before relying on either behaviour; the defaults have changed over versions.

This is the kind of outbound telemetry worth auditing on any privacy server — the same reasoning applies to monitoring stacks that ship data to third-party agents. On IronBalkans you can run either tool on full-root KVM with no account metadata behind it: signup needs no email, name, or ID, payment is Monero, Bitcoin or Litecoin, and the server is live in under 60 seconds — so the only outbound connections from your box are the ones you chose to allow.

The conflict nobody warns you about: log minimization

If you anonymize client IPs in nginx logs or truncate the journal aggressively to limit what's stored on disk, you've also removed the input these tools need. A fail2ban jail reading an nginx log where addresses are masked to 203.0.113.0 will either ban nothing or ban an entire netblock.

The workable compromise is two log streams:

  • A short-lived raw stream with real IPs, retained for minutes to hours, readable only by root and the detection agent. Long enough for a 10-minute findtime window, short enough that a seizure or compromise yields little.
  • A retained analytics stream with addresses anonymized, which is what survives rotation.

In nginx that means a second log_format and a second access_log directive with a tight logrotate policy on the raw file. This complements rather than contradicts a log minimization setup — the goal was never zero logs, it was zero unnecessary retention.

Common mistakes

Treating bans as the primary defense. A ban triggers after failures, which means credentials are being guessed. If SSH accepts passwords at all, you're relying on the attacker being impatient. Keys-only authentication removes the attack entirely; everything in SSH hardening that actually reduces risk outranks installing fail2ban.

Port-knocking logic via obscurity. Moving SSH to port 2222 cuts log volume dramatically but stops no targeted attacker. It's housekeeping, not security.

Forgetting Docker. Published container ports bypass the INPUT chain, so bans inserted there never apply to containerized services. You need rules in DOCKER-USER or bind containers to localhost behind a reverse proxy.

Running both tools at once. Two daemons writing competing firewall sets for the same services creates bans that are hard to trace and unban. Pick one.

Unbounded ban lists. Very long bantimes plus heavy scanning grows nftables sets to tens of thousands of entries. Use timeouts on the set, and prefer bantime.increment (fail2ban) over a flat multi-week ban.

What neither tool stops

  • Low-and-slow distributed credential stuffing. Three attempts per IP across 10,000 IPs never crosses a threshold. Rate limiting per account plus MFA handles this; IP banning doesn't.
  • Application vulnerabilities. An unauthenticated RCE succeeds on the first request. Patching and service sandboxing are the controls there.
  • Stolen keys or sessions. A valid login looks valid.
  • Volumetric DDoS. Your firewall drops packets after they've already saturated the link. Upstream filtering — included on every IronBalkans plan — is the layer that matters; a ban list is not a flood defense.

Pros and cons

fail2ban CrowdSec
Resource use Minimal; fine on a 1 GB VPS Agent + bouncer, typically ~100–200 MB RSS
Setup effort Low for SSH, regex work for custom apps Low with collections, more concepts to learn
External dependency None Central API by default
Pre-emptive blocking No Yes, via community blocklist
Best fit Single hardened server Multi-service or multi-server setups

On the smallest plans, memory is the deciding factor: if you're already tight on a 1 GB instance, fail2ban is the sane choice.

FAQ

Does fail2ban still get maintained? Yes. It's less fashionable than CrowdSec but actively packaged and perfectly adequate for log-based banning.

Will banning break legitimate users behind CGNAT? It can. Mobile carriers share addresses, so a short bantime and a higher maxretry on public-facing web logins avoids collateral damage.

Can I use CrowdSec's blocklist without running the agent? Blocklist access is tied to the API relationship; check current upstream terms before designing around it.

Do I need this if SSH is only reachable over WireGuard? No. If the public firewall drops port 22 entirely, there are no failed logins to count — that's a stronger control than any ban list.

Get started

Brute-force mitigation is the easy part. It only pays off on top of keys-only SSH, a firewall you've actually tested, and a provider that doesn't hand your identity to anyone who asks. If you want a clean server to build that stack on — full root KVM in Bucharest, dedicated IPv4 and a /64, DDoS filtering included, from €4.99/mo paid in XMR, BTC or LTC with no email or ID on file — spin one up in under a minute and start from a hardened baseline instead of retrofitting one.

Written by IronBalkans. Last reviewed Nov 5, 2026.