Securing Your HomeLab with CrowdSec on Docker: Collaborative Intrusion Prevention

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

Approach Comparison: Static Firewalls vs. Fail2ban vs. CrowdSec

The moment you open ports 80 and 443 for Nextcloud, Vaultwarden, or Plex, the automated scans begin. Within minutes, bots start probing for exposed admin panels, known CVEs, and weak SSH passwords. When protecting self-hosted services, homelab operators typically choose between three strategies:

  • Static Firewall Rules (UFW, iptables, pfSense): Blocking raw ports or entire country IP ranges works well at the edge. However, static rules have zero application awareness. An open port 443 firewall rule cannot tell the difference between you opening Nextcloud and an attacker spraying directory traversal payloads (like ../../etc/passwd) across your reverse proxy.
  • Fail2ban: The long-time homelab standard. It monitors text logs with regex rules and adds temporary bans via local firewall chains. It works reliably on standalone Linux boxes, but it has a blind spot: it runs in total isolation. If a botnet IP scans 10,000 servers across the internet before hitting yours, Fail2ban still ignores it until your machine gets hammered directly. In Docker setups, configuring Fail2ban also means wrestling with host path log mounts, systemd journal forwarding, and fragile regex patterns.
  • CrowdSec (Collaborative Intrusion Prevention): CrowdSec takes the core log-parsing idea of Fail2ban and pairs it with distributed threat intelligence. When an IP attacks any CrowdSec instance worldwide, that signal is analyzed. If consensus is reached, the offending IP is pushed to a shared global blocklist. CrowdSec also separates detection (the Security Engine) from enforcement (Bouncers), which makes it much cleaner to run across containerized stacks.

Pros and Cons of Running CrowdSec on Docker

Running the CrowdSec Security Engine inside Docker alongside your containers keeps deployment simple, though you should keep a couple of trade-offs in mind.

Pros

  • Decoupled Architecture: The CrowdSec engine runs in user space and handles only log analysis. It never needs raw kernel firewall privileges. Enforcement is offloaded to lightweight bouncers running on the host, at your reverse proxy (like Traefik plugins or Nginx modules), or even on your router.
  • Proactive Community Defense: Your server benefits from attacks detected elsewhere. If a rogue scanner in another region targets thousands of WordPress sites, your engine pulls that ban decision during its regular update cycles before the bot ever touches your domain.
  • Clear YAML Rules: Scenarios, parsers, and remediation profiles use readable YAML files. This makes them easy to version-control in Git and deploy across multiple nodes with Docker Compose.
  • Minimal Resource Usage: The Go engine is fast and lightweight. In most homelabs handling under 50 requests per second, the container idles at roughly 60MB of RAM and negligible CPU usage.

Cons

  • Multi-Component Setup: Getting started takes more effort than running a simple apt install fail2ban. You need to understand how the Local API (LAPI), log acquisition (acquis.yaml), hub collections, and bouncers communicate with one another.
  • Docker IP Masquerading Gotchas: If your reverse proxy or Docker bridge network does not forward true client IPs (using headers like X-Forwarded-For or X-Real-IP), CrowdSec will see the internal bridge gateway (e.g., 172.18.0.1) and risk banning your own proxy network.

Recommended Setup for HomeLab Environments

Here is a clean, practical layout for a single-node homelab:

  1. CrowdSec Security Engine (Docker): Runs as a container, exposing its Local API (LAPI) on 127.0.0.1:8080. It mounts read-only log directories from your reverse proxy (Nginx Proxy Manager, Traefik, or Caddy) alongside host auth logs.
  2. Host Firewall Bouncer (Native Service): Installed directly on the host OS as an iptables/nftables systemd service. It queries the container’s LAPI and drops malicious packets at the host kernel level before traffic ever reaches your Docker containers.
  3. Hub Collections: Pre-built rule packs installed inside the container that handle SSH brute-force protection, Linux auth parsing, generic HTTP exploits, and specific reverse proxy access logs.

This split keeps the log analysis contained inside Docker while ensuring actual network packet dropping happens where it belongs: directly on the host network layer.

Step-by-Step Implementation Guide

Step 1: Set Up Directory Structure and Docker Compose

Create a dedicated folder on your host for CrowdSec configurations and SQLite databases:

sudo mkdir -p /opt/crowdsec/config /opt/crowdsec/data
cd /opt/crowdsec

Create a docker-compose.yml file:

services:
  crowdsec:
    image: crowdsecurity/crowdsec:v1.6.3
    container_name: crowdsec
    restart: unless-stopped
    environment:
      - GID=1000
      - COLLECTIONS=crowdsecurity/linux crowdsecurity/sshd crowdsecurity/nginx crowdsecurity/http-cve
    volumes:
      - ./config:/etc/crowdsec
      - ./data:/var/lib/crowdsec/data
      - /var/log/auth.log:/var/log/auth.log:ro
      - /var/log/syslog:/var/log/syslog:ro
      - /var/log/nginx:/var/log/nginx:ro
    ports:
      - "127.0.0.1:8080:8080"
    security_opt:
      - no-new-privileges:true

Step 2: Configure Log Acquisition

CrowdSec needs to know which log files to watch and what format to expect. Create /opt/crowdsec/config/acquis.yaml:

filenames:
  - /var/log/auth.log
  - /var/log/syslog
labels:
  type: syslog
---
filenames:
  - /var/log/nginx/*.log
labels:
  type: nginx

Launch the container in the background to initialize the default configurations and database:

docker compose up -d

Step 3: Verify Collections and Parsers

Use the CrowdSec command-line tool (cscli) inside the container to make sure your parsers and scenarios loaded properly:

# Check installed scenario collections
docker exec crowdsec cscli collections list

# Confirm active log parsers
docker exec crowdsec cscli parsers list

# Review real-time processing metrics
docker exec crowdsec cscli metrics

Step 4: Install the Host Firewall Bouncer

The container detects threats, but it needs an enforcement tool to drop malicious traffic on the host. First, generate a bouncer API key inside the container:

docker exec crowdsec cscli bouncers add host-firewall-bouncer

Copy the generated key from the terminal output. Next, add the official repository and install the firewall bouncer on your host (Debian/Ubuntu example):

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt-get install -y crowdsec-firewall-bouncer-iptables

Open /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml on the host and configure your LAPI connection:

api_url: http://127.0.0.1:8080/
api_key: YOUR_GENERATED_API_KEY_HERE
inactivity_threshold: 10s
ipset_type: nethash

Start and enable the bouncer service:

sudo systemctl restart crowdsec-firewall-bouncer
sudo systemctl status crowdsec-firewall-bouncer

Step 5: Test Detection and Rule Enforcement

Verify that your local setup communicates with the global network and correctly applies decisions:

# List active local and community bans
docker exec crowdsec cscli decisions list

# Manually test a 10-minute ban on a documentation test IP (RFC 5737)
docker exec crowdsec cscli decisions add --ip 192.0.2.1 --type ban --duration 10m --reason "Manual test ban"

# Verify the test IP was added to the kernel ipset table on the host
sudo ipset list | grep 192.0.2.1

# Clean up the test ban
docker exec crowdsec cscli decisions delete --ip 192.0.2.1

With this setup running, your homelab now actively monitors incoming traffic, pulls community threat lists automatically, and drops bad actors at the host firewall before they can reach your self-hosted containers.

Share: