Securing Your Self-Hosted Apps with Fail2Ban and CrowdSec

Securing Your Self-Hosted Apps with Fail2Ban and CrowdSec

We earn commissions when you shop through the links on this page, at no additional cost to you. Learn more.

The moment you expose a self-hosted app to the internet — whether it's Nextcloud, Vaultwarden, Gitea, or a plain SSH port — the bots find it within minutes. I've watched auth logs on a fresh VPS fill up with thousands of failed login attempts before I've even finished the initial setup. Fail2Ban has been the go-to reactive defence for years, but CrowdSec brings something different to the table: collaborative, crowd-sourced threat intelligence that blocks known bad actors before they even knock on your door. In this tutorial I'll show you how to run both in tandem, so you get the best of reactive banning and proactive blocking.

Why Use Both? Fail2Ban vs CrowdSec

Fail2Ban is a log-scraping daemon that watches files like /var/log/auth.log and /var/log/nginx/access.log, then fires iptables or nftables rules when it spots too many failures from a single IP. It's local, simple, and battle-tested. The downside is it's purely reactive — an attacker needs to fail against your server before you ban them.

CrowdSec is an agent + bouncer model. The agent parses your logs and detects attacks using a library of community-maintained scenarios. When it flags an IP, it reports it to the CrowdSec Central API, contributing to a shared blocklist. In return, your bouncer automatically drops traffic from IPs that have been flagged by the community globally — before they try anything on your machine. I think of Fail2Ban as your door lock and CrowdSec as knowing which burglars are already working your street.

Running them together is low overhead and genuinely complementary. Fail2Ban covers app-specific patterns that don't have a CrowdSec scenario yet; CrowdSec handles the crowd-sourced reputation layer.

Step 1: Install and Configure Fail2Ban

On Ubuntu 22.04 / 24.04 or Debian 12:

sudo apt update && sudo apt install fail2ban -y

# Copy the default config so your changes survive upgrades
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

sudo systemctl enable --now fail2ban

Edit /etc/fail2ban/jail.local to set your global defaults and enable the jails you need. Here's a minimal production-ready config I use on every new server:

# /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
banaction = iptables-multiport
backend = systemd

# Whitelist your own IP — replace with your real address range
ignoreip = 127.0.0.1/8 ::1

[sshd]
enabled  = true
port     = ssh
logpath  = %(sshd_log)s
maxretry = 3
bantime  = 24h

[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log

[nginx-botsearch]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 2
bantime  = 12h

# Nextcloud brute force — if you're running it
[nextcloud]
enabled  = true
port     = http,https
logpath  = /var/www/nextcloud/data/nextcloud.log
maxretry = 5
bantime  = 2h
filter   = nextcloud

The nextcloud filter ships with recent versions of Fail2Ban. For Vaultwarden or other apps you'll need to write a custom filter under /etc/fail2ban/filter.d/ — a quick topic for a follow-up post. After editing, reload:

sudo systemctl reload fail2ban

# Verify jails are active
sudo fail2ban-client status

# Check a specific jail
sudo fail2ban-client status sshd
Tip: Set bantime = -1 on your SSH jail to make bans permanent after the configured maxretry is exceeded. Combined with a non-standard SSH port and key-only auth, this makes brute-forcing SSH essentially pointless.

Step 2: Install CrowdSec Agent

CrowdSec maintains an official APT/RPM repository. Don't use the distro package — it's usually several major versions behind.

# Add the CrowdSec official repository
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash

sudo apt install crowdsec -y

# Check it's running and has detected your log sources
sudo systemctl status crowdsec
sudo cscli collections list

CrowdSec auto-detects many common services during install by inspecting running processes and known log paths. Run sudo cscli hub list to see what collections (bundled parsers + scenarios) are installed. You'll usually see crowdsecurity/linux and crowdsecurity/sshd at minimum.

Install additional collections for whatever you're running:

# Add collections for common self-hosted apps
sudo cscli collections install crowdsecurity/nginx
sudo cscli collections install crowdsecurity/nextcloud
sudo cscli collections install crowdsecurity/http-cve

# If you use Caddy instead of Nginx
sudo cscli collections install crowdsecurity/caddy

# Reload CrowdSec to pick up the new parsers
sudo systemctl reload crowdsec

Step 3: Install a CrowdSec Bouncer

The agent detects and reports attacks, but it's the bouncer that actually blocks traffic. For a bare-metal or VPS setup, the cs-firewall-bouncer is the right choice — it writes rules directly to nftables or iptables.

sudo apt install crowdsec-firewall-bouncer-nftables -y

# The bouncer registers itself automatically — verify it's connected
sudo cscli bouncers list

You should see your bouncer with a valid API key status. CrowdSec will now automatically push ban decisions (both local detections and community blocklist entries) to nftables in real time.

Watch out: If you're running Docker, the firewall bouncer operates at the host level but Docker's own iptables rules can bypass nftables depending on your kernel version. I strongly recommend setting DOCKER_OPTS="--iptables=false" and managing all Docker firewall rules manually, or using CrowdSec's dedicated Docker bouncer (crowdsecurity/cs-docker-bouncer) alongside the firewall bouncer.

Step 4: Register with the CrowdSec Console

Registering your instance with the CrowdSec Console (free tier available) gives you access to premium blocklists and a dashboard to see what's being blocked in real time. It takes about two minutes:

# Sign up at https://app.crowdsec.net/ then enroll your instance
sudo cscli console enroll YOUR_ENROLL_KEY

sudo systemctl restart crowdsec

# Confirm decisions are flowing in
sudo cscli decisions list

The decisions list command is something I check every morning. Seeing hundreds of pre-emptively blocked IPs — flagged by other CrowdSec users around the world — before any of them touched my logs is genuinely satisfying.

Step 5: Make Sure Fail2Ban and CrowdSec Don't Conflict

Both tools write to iptables/nftables. In practice they don't conflict — they create separate chains — but there are a few things worth setting explicitly:

In /etc/fail2ban/jail.local, ensure your banaction targets the iptables-multiport or iptables-allports action rather than nftables, since CrowdSec owns the nftables chains on my setups. Alternatively, use Fail2Ban's nftables-multiport action and a separate nftables table — just don't share table names.

# Check what's in your active nftables ruleset
sudo nft list ruleset | grep -E "crowdsec|f2b"

# Manually unban an IP in Fail2Ban (useful during testing)
sudo fail2ban-client set sshd unbanip 1.2.3.4

# Manually remove a CrowdSec decision
sudo cscli decisions delete --ip 1.2.3.4

Monitoring and Alerting

I pipe cscli metrics and fail2ban-client status output into a small shell script that posts to a Ntfy or Slack webhook nightly. But for a visual overview, the CrowdSec Console dashboard is hard to beat — it maps attack origins geographically and shows you which scenarios are firing most often. On a typical Nextcloud VPS I see credential-stuffing attempts from around 40 countries per week. Without these two layers running, a percentage of those would eventually find a weak account.

Tip: Subscribe to CrowdSec's crowdsecurity/firehol-level1 and crowdsecurity/tor-exit-nodes blocklists in the Console. They're free-tier lists that add tens of thousands of known-bad IPs to your bouncer's block decisions with zero log parsing overhead.

Running This on a VPS

Everything in this guide works equally well on a home server or a cloud VPS. If you're looking for a reliable, affordable VPS to host your self-hosted stack, I've had good experiences with DigitalOcean Droplets — the $6/month basic Droplet handles Nextcloud, Vaultwarden, and a full CrowdSec + Fail2Ban stack without breaking a sweat. Start spending more time on your projects and less time managing infrastructure: create your DigitalOcean account today.

DigitalOcean

Wrapping Up

Combining Fail2Ban and CrowdSec gives you a genuinely layered intrusion prevention setup that covers both reactive log-based banning and proactive crowd-sourced blocking. The install time for both tools together is under 15 minutes, and once configured they run silently in the background with almost zero maintenance overhead. My next recommended steps from here:

The internet is a hostile place for self-hosted services. These two tools are among the cheapest (free) and most effective defences you can layer on. Set them up on day one of any new server, not day ten.

Discussion