← Blog

Fail2ban vs CrowdSec in 2026: Which One to Install on Your VPS (and Where PSAD Fits)

Security · · 10 min read

Short answer: if your VPS only exposes SSH, install Fail2ban. If it serves public web traffic (nginx, Traefik, WordPress, anything on 80/443), install CrowdSec with a bouncer: its HTTP scenarios cover far more than Fail2ban's filters, and it blocks IPs other users have already reported. PSAD detects port scans, but it hasn't had a release since 2018, so treat it as a niche extra.

Whichever you pick, it is one layer of a VPS hardening and security strategy, not the strategy itself.


1. Fail2ban vs CrowdSec vs PSAD at a glance

Versions checked on 30 October 2026.

Fail2ban CrowdSec PSAD
Latest release 1.1.1 (August 2026) 1.8.2 (October 2026) 2.4.6 (August 2018)
How it detects Regex filters on log files or the systemd journal. Counts failures per IP in a time window YAML parsers and scenarios over log files, the journal or Docker container logs Reads iptables LOG lines from the kernel log and scores port scans
How it blocks Firewall rule (nftables, iptables, firewalld, ufw) or any scripted action, such as the Cloudflare API A separate bouncer: firewall (nftables, iptables, ipset), nginx, Traefik, HAProxy, Cloudflare Optional iptables blocking, off by default
Blocks IPs before they try you No Yes, through the community blocklist No
Resources One Python daemon. CPU grows with log volume Go engine, under 100 MB of RAM on small and medium setups according to its docs, plus the bouncer Perl daemon, plus the extra firewall logging it needs
Setup effort apt install and a few lines of jail.local Vendor repo, engine, bouncer, a collection per service iptables LOG rules and threshold tuning
Data sharing None Sends attacker IP, scenario, timestamp and machine ID by default. Not your logs. Can be turned off None
Free vs paid Free (GPL-2.0) Engine and bouncers free (MIT). Free console: 500 alerts/month, 2 months of history, 3 blocklists. Premium is billed per engine Free (GPL-2.0)
IPv6 Yes Yes Yes (ip6tables logs)
nftables Yes. Default banaction on Debian 13 Yes, dedicated bouncer package No, iptables only
Docker Bans go to INPUT and miss published container ports unless you use DOCKER-USER nftables bouncer filters forwarded traffic by default and the engine reads container logs Not designed for it

2. Fail2ban: one daemon, one regex, one ban

Fail2ban tails a log (or the systemd journal), matches each line against a filter, and counts failures per IP. With the stock settings, 5 failures within 10 minutes get that IP banned for 10 minutes. The ban is a firewall rule, or any command you define as an action.

The current upstream release is 1.1.1 (15 August 2026), the first since 1.1.0 in April 2024. Your distro probably ships something older: Debian 12 and Ubuntu 24.04 have 1.0.2, Debian 13 has 1.1.0. That's fine for SSH.

One trap on Debian 12: without rsyslog there is no /var/log/auth.log, and the sshd jail fails with "Have not found any log file for sshd jail". Read the journal instead. A sensible starting jail.local:

# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
# Repeat offenders get longer bans each time
bantime.increment = true
# Never ban yourself: add your own static IP
ignoreip = 127.0.0.1/8 ::1 203.0.113.10

[sshd]
enabled = true
backend = systemd

Then sudo systemctl restart fail2ban.

What it does well:

  • No network calls, no account, nothing leaves the server.
  • Ships filters for sshd, nginx, Apache, Postfix, Dovecot and dozens more.
  • Actions are just commands, so it can ban on the local firewall or push the ban to Cloudflare.

Where it falls short:

  • Purely reactive. Every new IP gets its 5 attempts before it's banned. A botnet that rotates addresses barely notices.
  • Every log line goes through Python regexes. On a quiet SSH log that's nothing. On a busy nginx access log it shows up in top.
  • For web attacks you end up writing and testing your own regexes.

3. CrowdSec: detection plus a shared blocklist

CrowdSec splits the job in two. The Security Engine parses logs and runs scenarios (brute force, path probing, CVE scans) to produce decisions. A bouncer enforces them: in the firewall, in nginx, in Traefik or at Cloudflare.

The engine alone blocks nothing. CrowdSec's own docs say so, and it's an easy one to miss: engine running, no bouncer, zero bans.

The current release is 1.8.2 (26 October 2026). Don't install it from the Debian or Ubuntu archive: those ship 1.4.6. Use the vendor repo:

curl -s https://install.crowdsec.net | sudo sh
sudo apt install crowdsec
sudo apt install crowdsec-firewall-bouncer-nftables

The installer tries to detect your services and install matching collections. Check with sudo cscli collections list and add what's missing, for example sudo cscli collections install crowdsecurity/nginx.

The community blocklist

This is CrowdSec's real advantage. Your engine reports what it detects, and in return it downloads a blocklist built from what other engines detected, filtered to the scenarios you run. Free users who contribute get the Community list (15,000 IPs). Free users who don't contribute get Lite (3,000 IPs). Paid plans get Premium (up to 60,000 IPs) without having to contribute. Only signals from unmodified Hub scenarios count as contributing.

What CrowdSec shares, and how to stop it

By default the engine sends "signal metadata" to CrowdSec's Central API: the scenario name and version, the timestamp, your machine ID and the offending IP. It does not send your log lines. It also reports which scenarios and bouncers you run.

To stop sharing, set this in /etc/crowdsec/config.yaml:

api:
  server:
    online_client:
      sharing: false

On the free plan that drops you to the Lite blocklist. Commenting out the whole online_client block disconnects from the Central API entirely, and you get no community blocklist at all.

Free vs paid

The engine and bouncers are open source (MIT) and free. The web console is optional. On the free Community plan it keeps 500 alerts per month with 2 months of history, and you can subscribe to 3 blocklists. Console Premium is billed per enrolled Security Engine and adds the Premium blocklist, longer retention and higher alert quotas. You don't need the console for anything in this article.

What it does well:

  • Blocks IPs already reported by other engines before they send a single request.
  • The HTTP collection catches path traversal, .env and .git probing, SQLi and XSS probing, WordPress scans and CVE probes out of the box.
  • Bouncers for the firewall, nginx, Traefik, HAProxy and Cloudflare.

Where it falls short:

  • More moving parts: engine, bouncer, collections, an outbound connection.
  • Distro packages are years old, so you depend on the vendor repository.
  • The blocklist works because you share. Turn sharing off and you're down to the 3,000-IP Lite list.

4. Which one should you install?

Small VPS, only SSH open

Fail2ban, with only the sshd jail. It's one package, no account and nothing to keep in sync.

Be honest about what it buys you. If you already log in with SSH keys only (PasswordAuthentication no, root login off) and the firewall exposes nothing else, a brute force can't succeed anyway. Fail2ban then shortens your logs and saves some CPU on pointless handshakes. Worth five minutes, but keys and the firewall are your defence, not Fail2ban. More on that in Fail2ban vs SSH keys.

Web server with nginx

CrowdSec with the firewall bouncer. The scanners that hit every public IP looking for /.env, /wp-login.php or the latest CVE are exactly what the community blocklist and the HTTP scenarios cover. Fail2ban has nginx filters (nginx-http-auth, nginx-botsearch, nginx-limit-req, nginx-bad-request, nginx-forbidden), and they work, but anything beyond that is regex you write and maintain. If you want WAF-style blocking inside nginx, CrowdSec also has an nginx bouncer.

Behind Cloudflare

The proxy changes the problem. Your server sees Cloudflare's IPs, not the visitor's, so:

  1. Restore the real IP before logging. In nginx, use the realip module with Cloudflare's published ranges. Without this, CrowdSec sees only CDN addresses, which its default whitelists ignore, so you get no alerts at all.
# One line per range from https://www.cloudflare.com/ips/
set_real_ip_from 173.245.48.0/20;
real_ip_header CF-Connecting-IP;
  1. Block where the traffic enters. A ban in your server's firewall doesn't stop proxied requests, because they arrive from Cloudflare. Block at Cloudflare (Fail2ban's cloudflare-token action, or CrowdSec's Cloudflare Worker bouncer, which CrowdSec says works best on a paid Workers plan) or inside nginx with CrowdSec's nginx bouncer.
  2. Accept 80/443 only from Cloudflare's ranges, so nobody skips the proxy.

SSH isn't proxied, so an sshd jail or the firewall bouncer works there as usual. Pick CrowdSec if you want the HTTP scenarios. Fail2ban for SSH plus Cloudflare's own WAF for the web is also a reasonable setup.

Docker-heavy host

CrowdSec. Two reasons:

  • Where the ban lands. Traffic to a published container port goes through the FORWARD path, not INPUT. Fail2ban bans in INPUT by default, so the container stays reachable from a banned IP. It's the same mechanism that lets Docker bypass UFW. CrowdSec's nftables bouncer hooks both input and forward by default. In iptables mode, add DOCKER-USER to iptables_chains in the bouncer config.
  • Where the logs live. Container logs aren't in /var/log. CrowdSec has a Docker data source that reads them directly. With Fail2ban you have to route them somewhere it can read.

If you stay on Fail2ban, set chain = DOCKER-USER on the jail with an iptables-based action such as banaction = iptables-allports. The allports variant matters because in DOCKER-USER the destination port is the container's internal one, not the published one.

Both at once?

You can, but there's little point in parsing the same log twice. If you want both, split by service: Fail2ban on SSH, CrowdSec on the web stack.


5. Where PSAD fits

PSAD reads the iptables LOG entries in your kernel log and scores them to detect port scans (SYN, FIN, NULL, Xmas and so on). It can block automatically, but that's off by default.

Its status matters more than its features. The last release is 2.4.6 from August 2018, and the last commit on GitHub is from November 2018. Debian 12 and 13 still package it. The code only knows iptables; there's no nftables support.

On a VPS that only opens the ports it needs, scans are background noise. Knowing you were scanned doesn't change what you should do. Skip PSAD unless you run an iptables setup and specifically want scan alerts.


6. Is it actually banning anything?

Installing is half the job. Check that bans happen.

Fail2ban:

sudo fail2ban-client status          # active jails
sudo fail2ban-client status sshd     # failures and bans for sshd
sudo fail2ban-client banned          # every banned IP, by jail

In status sshd, look at "Total failed" and "Total banned". If your auth log shows failed logins but "Total failed" stays at 0, the jail is reading the wrong source (the Debian 12 auth.log case above). With an nftables banaction, sudo nft list table inet f2b-table shows the actual firewall set.

CrowdSec:

sudo cscli decisions list            # local bans
sudo cscli decisions list -a         # including the community blocklist
sudo cscli bouncers list             # is a bouncer registered and pulling?
sudo cscli metrics show bouncers     # traffic actually dropped, by origin
sudo cscli metrics show acquisition  # are your logs being read and parsed?

No bouncer in cscli bouncers list means nothing is being blocked, whatever decisions list says. Zero "Lines parsed" in the acquisition metrics means CrowdSec is reading a log it can't parse, or the wrong file.

To compare memory on your own box, systemctl status fail2ban crowdsec prints a "Memory" line for each running service.


7. Check your VPS with SecuryBlack

Whichever tool you choose, it only helps if it's running and the rest of the server is sane.

Want a first look without signing up? Run the free read-only check. It doesn't send anything anywhere:

curl -fsSL https://securyblack.com/audit.sh | sudo sh

SecuryBlack is a hosted panel that monitors and manages your own VPS. Its security audit, run by our open-source Rust agent FerroSentry, checks SSH root and password login, the firewall, Docker ports published past UFW, whether Fail2ban or CrowdSec is installed, databases listening beyond localhost and pending security updates. Each finding comes with the exact command to fix it, and one-click hardening only applies after you confirm. It's free during the beta.

Blog

Find out what's wrong with your server

Connect your first VPS and get the security audit. Free during the beta.