Hardening

Fail2ban vs CrowdSec: what each one does

Both read your logs and block addresses that misbehave. They differ in where the ban decision comes from, how many parts you run, and what leaves the machine. For SSH, the more useful question is whether the port should be reachable at all.

The short answer

Fail2ban reads log files on one server, matches failed logins with regular expressions, and bans the source address in that server's firewall for a set time. CrowdSec detects the same behaviour with parsers and scenarios, leaves blocking to separate components called bouncers and, unless you opt out, trades attacker addresses with its network for a shared blocklist. Fail2ban suits a small, self-contained setup; CrowdSec suits public web services and fleets. If SSH is only reachable over a private network, neither adds much for SSH.

Fail2ban reads one server's logs

The project's one-line description says it scans log files and bans IP addresses that show too many failed login attempts, by updating firewall rules for a configurable amount of time. The configuration is split into jails, filters and actions.

  1. JailsA jail ties one service to a filter, an action and three numbers. The upstream defaults for bantime, maxretry and findtime ban an address for 10 minutes after 5 failures within 10 minutes. Upstream ships every jail disabled. You switch on what you need in jail.local or a file in jail.d, never in jail.conf, which a package update may overwrite.
  2. FiltersFiles in filter.d holding failregex patterns, Python regular expressions that recognise a failure line and capture the client address. The stock set covers sshd, Apache, nginx, Postfix, Dovecot and dozens of other services.
  3. ActionsFiles in action.d that run the ban and unban commands: nftables, iptables, firewalld, ufw and others, plus actions that ban at Cloudflare, send mail, or report the address to AbuseIPDB.

A minimal jail.local, following the example in the stock jail.conf header:

# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h

[sshd]
enabled = true

The current release is 1.1.1, published on 15 August 2026. Its changelog makes nftables the default ban action for the stock Debian paths and switches the sshd jail to reading the systemd journal. Debian 13 already ships fail2ban 1.1.0 with those settings, and its package also enables the sshd jail out of the box. Installing fail2ban on Debian 13 starts banning SSH failures immediately, with no jail.local at all and no /var/log/auth.log on the system.

Everything fail2ban knows comes from the logs on that one machine, and each server keeps its own ban list. It can report offenders outward, but nothing in the stock actions pulls a shared list back in. It is licensed GPL v2 or later. The README says fail2ban can reduce the rate of incorrect authentication attempts, but it cannot eliminate the risk presented by weak authentication.

CrowdSec splits detection from blocking

CrowdSec splits the same job into separate components. The Security Engine is the Log Processor plus the Local API, and a separate component does the blocking.

  1. Log ProcessorReads logs through an acquisition configuration, parses each event and enriches it with the source address, service, GeoIP and ASN, then matches events against scenarios, which correlate behaviour over time, such as many failed logins from one address. It can also inspect HTTP requests directly against AppSec (WAF) rules.
  2. Local APIStores alerts and turns them into decisions using profiles. The default profile bans the address for 4 hours.
  3. Remediation components (bouncers)Separate programs that pull decisions from the Local API and enforce them: a firewall bouncer for iptables, nftables, ipset or pf, or bouncers for nginx, Traefik, HAProxy and others that act at the HTTP layer. Some can serve a CAPTCHA instead of a block.
  4. Central APICrowdSec's service, where your engine sends signals and from which it receives the community blocklist.

New installs go wrong at that split. CrowdSec's concepts page says the engine detects and the bouncers block, so an engine with no bouncer registered produces alerts and blocks nothing. The engine is written in Go. CrowdSec's FAQ states that it and the remediation components are MIT-licensed.

CrowdSec data sharing and pricing

When a scenario fires, the Local API sends what CrowdSec calls signal meta-data to the Central API unless you opt out: the scenario name, its hash and version, the timestamp of the decision, your machine_id, and the offending IP address with geolocation where available. At login the engine also sends its list of enabled scenarios, and it reports the names and versions of your engines and bouncers. The documentation does not list log lines among what is sent, and none were in the signals we captured.

The Central API documentation also says the engine does not push custom scenarios, modified hub scenarios, which CrowdSec calls tainted, or manual decisions unless you enrol in the console. CrowdSec 1.8.1 did not behave that way in our test. Its default console.yaml sets share_custom and share_tainted to true, and an engine that was registered but never enrolled sent signals from a custom scenario and from an edited hub scenario to a local stand-in for the Central API. It did not send manual decisions. In the engine's source, shouldShareAlert in apic.go checks those console flags and never checks enrolment. To share only unmodified hub scenarios, run sudo cscli console disable custom tainted. After we ran it, only the unmodified scenario's signal arrived.

To opt out completely, comment out the online_client section of /etc/crowdsec/config.yaml. You then lose the community blocklist, which is the main reason to choose CrowdSec. For a partial opt-out, set sharing: false under online_client. In our test it stopped signals entirely while the engine kept pulling the blocklist, and CrowdSec's configuration reference warns that turning sharing off affects how many addresses the blocklist gives you. If you keep GDPR records of what data leaves your systems, the shared attacker addresses belong in them.

The blocklist you receive depends on what you give. Free users who regularly contribute signals get the Community Blocklist, 15,000 addresses matched to the scenarios they run. Free users who do not contribute get the Lite version, capped at 3,000. Paying users get the Premium version with no size limit, contributing or not.

The engine and bouncers cost nothing. CrowdSec charges for the products around them: Console Premium, priced per enrolled Security Engine; Platinum Blocklists, listed from $1,900 a month for small and medium businesses; and IP reputation API access from $49 a month for 5,000 queries. Those are the figures on CrowdSec's pricing page in September 2026.

Side by side

Fail2banCrowdSec
DetectionPython regular expressions over log linesParsers and scenarios over log lines, plus optional HTTP inspection (AppSec)
EnforcementBuilt in, through the action (nftables, iptables, firewalld, ufw and others)A separate bouncer: firewall, web server or reverse proxy
Default ban10 minutes after 5 failures in 10 minutes4 hours per decision
What a ban blocks (tested defaults)The jail's ports only, with a rejectAll traffic from the address, dropped (nftables bouncer)
Shared intelligenceNone built in; can report outwardCommunity blocklist in exchange for signals; opt-out possible
Parts to runOne serviceEngine (Log Processor and Local API) plus one or more bouncers
LicenceGPL v2 or laterMIT for engine and bouncers; console extras and premium lists are paid

Resource usage is missing on purpose. CrowdSec's FAQ says the engine should use under 100 MB of memory in a small to medium setup. Fail2ban publishes no comparable figure, so there is nothing like-for-like to put in a table. We measured one test box only. A few minutes after start, with 54 scenarios and one journal source, the CrowdSec 1.8.1 engine was using about 145 MB of resident memory and its nftables bouncer about 21 MB, while fail2ban 1.1.0 with two jails used about 26 MB.

First check whether anyone can reach the port

Most of these comparisons are about banning SSH brute-force attempts. If SSH is not on the public internet, that argument is moot. On the machines Wardio manages, admin access runs over Tailscale, a private mesh network, and SSH is not reachable from the internet at all, so there are no internet-sourced failures for an sshd jail to count. CrowdSec's documentation makes the same point from the other side. It lists VPN-only access and IP allowlisting with a default-deny policy as setups that can land an engine on the Lite blocklist, and says they reduce reliance on the blocklist in the first place.

Even with SSH still public, key-only authentication removes what a brute-force ban protects against. A ban then mostly shrinks log noise. That has some value, but it is not a control on the order of turning passwords off, which is why our guide to securing a VPS puts key-only SSH and a private admin network ahead of anything else.

OpenSSH now penalises repeat offenders itself. On Debian 13, OpenSSH 10.0 has PerSourcePenalties switched on by default, and in our test sshd began dropping connections from an address after repeated failed logins. Fail2ban's stock sshd filter, in its normal mode, does not match the line sshd logs when it drops a penalised connection.

Bans still help on services that must stay public and accept credentials: a shop's customer login, a WordPress admin page, mail authentication in Postfix or Dovecot, a web control panel. Either tool only helps there if the conditions below hold.

The application has to log the failure. Both tools act only on what appears in a log. If a login form does not write a line containing the client address when a password is wrong, neither tool will ever see the attempt.

The address in the log has to be the attacker's. Behind a CDN or reverse proxy the server may log the proxy's address, and banning that blocks your real visitors. If it logs the real client address from a forwarded header instead, a firewall ban on the origin server does nothing, because the connection still arrives from the proxy. Ban where the real address is visible. That is at the CDN, where fail2ban ships a Cloudflare action, or in the web server or proxy, where CrowdSec has bouncers for nginx, Traefik and HAProxy.

Choosing between them

This is opinion, for setups of one to a few servers.

Fail2ban if you have one or two servers, a handful of public services, and prefer that nothing leaves the machine. It is in the distribution repositories and runs as one service, and a few configuration files define its behaviour.

CrowdSec if you run public web applications that attract constant scanning or several servers that should share decisions, or if you want blocking at the HTTP layer rather than the firewall. You accept more components and share attacker addresses with a third party in exchange for the blocklist.

Neither, for SSH, if you can take SSH off the internet instead. Put the time into patching. An unpatched service with a public exploit does not produce failed logins, so no login ban notices it. If automatic updates are switched on but not doing much, this checklist finds out why.

Check that it does anything

Whichever you run, confirm that it matches log lines and enforces bans:

# fail2ban: failures and banned addresses for the sshd jail
sudo fail2ban-client status sshd

# CrowdSec: what is parsed, what is decided, and who enforces it
sudo cscli metrics
sudo cscli decisions list
sudo cscli bouncers list

If cscli bouncers list comes back empty, CrowdSec detects but blocks nothing. A bouncer in the list is not proof on its own, because a firewall reload can remove its rules, as the gotchas below describe. If the fail2ban jail shows zero failures on a service you know is being attacked, the filter is not matching your log format or the jail is reading the wrong log. A jail pointed at a log file that does not exist does not show zero. In that case fail2ban refuses to start at all.

Gotchas

How this was tested. On 23 September 2026 we ran both tools in Docker containers running systemd on Debian 13, from the debian:trixie image, and attacked them from a second container. We ran fail2ban 1.1.0, Debian package 1.1.0-8, with OpenSSH 10.0p2 and nftables 1.1.3, and CrowdSec 1.8.1 with crowdsec-firewall-bouncer-nftables 0.0.36 from CrowdSec's repository, plus the official CrowdSec Docker image at 1.8.1. The proxy cases used nginx in front of an nginx origin. We checked fail2ban 1.1.1 from its released files and did not install it. We captured what CrowdSec shares with a local fake Central API while blocking the test box's other outbound traffic, and the memory figures come from that one box. Not tested: the pf, Traefik, HAProxy and CAPTCHA bouncers, AppSec rules, console enrolment, Debian 12, Tailscale, and blocklist sizes and prices. Those need a contributing account over time, so we took them from CrowdSec's documentation and pricing page.

Sources

  1. Fail2Ban README, fail2ban project, GitHub
  2. Fail2Ban ChangeLog, version 1.1.1, fail2ban project, 15 August 2026
  3. jail.conf, version 1.1.1, fail2ban project
  4. jail.conf(5) manual page, Debian 13
  5. defaults-debian.conf in fail2ban 1.1.0-8, Debian
  6. Concepts, CrowdSec documentation, Security Engine v1.8
  7. Profiles, CrowdSec documentation
  8. Firewall remediation component, CrowdSec documentation
  9. Central API: data exchanged, CrowdSec documentation
  10. Community Blocklist, CrowdSec documentation
  11. Configuration file reference (online_client sharing), CrowdSec documentation
  12. apic.go (shouldShareAlert), Security Engine v1.8.1 source, CrowdSec, GitHub
  13. Security Engine troubleshooting and FAQ, CrowdSec documentation
  14. Pricing, CrowdSec, accessed September 2026

Questions

What is fail2ban?

Fail2ban is an open-source service, licensed GPL v2 or later, that scans log files such as the SSH authentication log, counts failures per IP address using regular expressions, and bans addresses that exceed a limit by adding firewall rules for a set time. The upstream default is a 10 minute ban after 5 failures within 10 minutes. Each server runs its own copy with its own ban list. The current release is 1.1.1, from August 2026.

Is fail2ban worth it?

Yes, on a public service that accepts passwords and logs failed attempts. It is cheap to run and slows brute-force guessing. For SSH it matters less than people expect. With key-only authentication, password guessing cannot succeed anyway, and if SSH is reachable only over a VPN or private mesh there is nothing to ban. Its own README says it reduces the rate of bad logins but cannot eliminate the risk of weak authentication.

Does CrowdSec send my logs to CrowdSec?

CrowdSec's documentation does not list log lines among the data it sends, and none were in the signals we captured in testing. When a scenario fires, the Local API sends signal meta-data: the scenario name, its hash and version, the decision timestamp, your machine ID and the offending IP address with geolocation where available. It also reports your enabled scenarios and engine and bouncer versions. In our test of version 1.8.1, the engine sent signals from custom and modified scenarios by default without console enrolment, although the documentation says it does not. Running sudo cscli console disable custom tainted stops that. Setting sharing: false under online_client stops signals but keeps the blocklist download. Commenting out the online_client section opts out completely, and you lose the community blocklist.

Is CrowdSec free?

The Security Engine and its remediation components are MIT-licensed and free to run. Free users who regularly share signals get a community blocklist of about 15,000 addresses matched to their scenarios. Free users who do not share get a Lite list capped at 3,000. CrowdSec charges for Console Premium per enrolled engine, for Platinum blocklists, listed from $1,900 a month for small and medium businesses, and for IP reputation API access.

Not sure what on your server is reachable from the internet?

The free server review looks at what is exposed, which decides whether bans matter at all. Pricing.

Free server review