The Complete Self-Hosting Security Stack: Fail2Ban + CrowdSec + Authelia

Running self-hosted services on the open internet without a security stack is like leaving your front door unlocked in a busy neighborhood. Individual tools help, but real protection comes from layering defenses so each one covers the gaps of the others.

This guide builds a complete security stack using three open-source tools:

  • Fail2Ban — reactive log-based banning for brute-force attacks
  • CrowdSec — community-powered threat intelligence and behavioral detection
  • Authelia — authentication portal with single sign-on and multi-factor authentication

Together, they give you intrusion prevention, shared threat intelligence, and access control. All running in Docker, all free.

Why Layer Security?

No single tool catches everything. Here’s what each layer handles:

ThreatFail2BanCrowdSecAuthelia
SSH brute force✅ Bans after N failures✅ Bans + shares IP globally
Known malicious IPs✅ Community blocklists
Web app credential stuffing✅ Log-based detection✅ Behavioral scenarios✅ Rate limiting + MFA
Unauthorized service access✅ Auth portal + policies
Automated scanners/botsPartial✅ HTTP scenario detection✅ Blocks unauthenticated access
Stolen credentials✅ MFA stops reuse

Fail2Ban is your fast, proven reactive layer. CrowdSec adds proactive community intelligence. Authelia ensures that even if an attacker gets past the network layer, they still need valid credentials and a second factor.

Prerequisites

Before starting, you’ll need:

  • A Linux server (Ubuntu 22.04/24.04 or Debian 12 recommended)
  • Docker and Docker Compose installed
  • A reverse proxy (Nginx Proxy Manager, Traefik, or Caddy)
  • A domain name with DNS pointing to your server
  • Basic familiarity with YAML and the command line

This guide assumes you’re running services behind a reverse proxy with HTTPS already configured.

Architecture Overview

Here’s how the stack fits together:

InCBFtroaeouCEirR(wnrnlneNdcog2evgSewiBteierdn(arncSelnsxeoe/cgTsPr)raoeAAixfuupyitttkhha(/eb4ClGl4aiaCBe3datols)demo/ymcn)ukfnltiiatsbytlP(GserJisoettleelacy,tfeiednt,cS.eN)revxitccelsoud,

Traffic hits your reverse proxy first. CrowdSec’s bouncer drops known-bad IPs before they touch anything. Authelia challenges unknown users with login + MFA. Fail2Ban watches logs from all layers and bans repeat offenders at the firewall level.

Step 1: Set Up Fail2Ban

Fail2Ban is the foundation. It watches logs and bans IPs that trigger too many failures.

Install Fail2Ban

sudo apt update && sudo apt install -y fail2ban

Configure the SSH Jail

Create a local override file (never edit the main config directly):

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
banaction = nftables-multiport
banaction_allports = nftables-allports

[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 3
bantime = 4h

Add a Jail for Your Reverse Proxy

If you’re running Nginx Proxy Manager or plain Nginx:

sudo nano /etc/fail2ban/jail.local

Append:

[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 1h

[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 24h

Create an Authelia Fail2Ban Filter

This catches failed login attempts at the Authelia layer:

sudo nano /etc/fail2ban/filter.d/authelia.conf
[Definition]
failregex = ^.*Unsuccessful 1FA authentication attempt by user .*remote_ip="?<HOST>"?.*$
            ^.*Unsuccessful 2FA authentication attempt by user .*remote_ip="?<HOST>"?.*$
ignoreregex =

Add the jail:

[authelia]
enabled = true
logpath = /path/to/authelia/authelia.log
port = http,https
filter = authelia
maxretry = 3
bantime = 4h
findtime = 5m

Start and Verify

sudo systemctl enable --now fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd

You should see the jails listed as active with zero currently banned IPs (hopefully).

Step 2: Add CrowdSec for Threat Intelligence

CrowdSec goes beyond Fail2Ban by sharing threat data with a global community. When someone else’s server gets attacked by an IP, your server learns about it too.

Deploy CrowdSec with Docker Compose

Create a directory for your security stack:

mkdir -p ~/security-stack && cd ~/security-stack
# docker-compose.yml
services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    environment:
      - COLLECTIONS=crowdsecurity/nginx crowdsecurity/http-cve crowdsecurity/linux
      - GID=1000
    volumes:
      - ./crowdsec/config:/etc/crowdsec
      - ./crowdsec/data:/var/lib/crowdsec/data
      - /var/log:/var/log:ro
      # Mount your reverse proxy logs:
      - /path/to/nginx/logs:/var/log/nginx:ro
    networks:
      - security

  crowdsec-bouncer:
    image: fbonalair/traefik-crowdsec-bouncer:latest
    container_name: crowdsec-bouncer
    restart: unless-stopped
    environment:
      - CROWDSEC_BOUNCER_API_KEY=${CROWDSEC_API_KEY}
      - CROWDSEC_AGENT_HOST=crowdsec:8080
    depends_on:
      - crowdsec
    networks:
      - security

networks:
  security:
    external: true

Note: The bouncer image depends on your reverse proxy. Use crowdsecurity/nginx-bouncer for Nginx, or the Traefik bouncer for Traefik. Check the CrowdSec Hub for your setup.

Register the Bouncer

docker exec crowdsec cscli bouncers add my-bouncer

Copy the generated API key into your .env file:

echo "CROWDSEC_API_KEY=your-generated-key-here" >> .env

Subscribe to Community Blocklists

CrowdSec’s free tier includes community-curated blocklists. Enroll your instance:

docker exec crowdsec cscli console enroll your-enrollment-key

Get your enrollment key from app.crowdsec.net after creating a free account.

Install Relevant Parsers and Scenarios

docker exec crowdsec cscli collections install crowdsecurity/nginx
docker exec crowdsec cscli collections install crowdsecurity/http-cve
docker exec crowdsec cscli collections install crowdsecurity/linux
docker exec crowdsec cscli collections install crowdsecurity/sshd

Verify It’s Working

# Check decisions (current bans)
docker exec crowdsec cscli decisions list

# Check alerts
docker exec crowdsec cscli alerts list

# Check metrics
docker exec crowdsec cscli metrics

Within a few hours, you’ll start seeing community-sourced bans supplementing your local detections.

Step 3: Deploy Authelia for Authentication

Authelia adds the access control layer. Every request to a protected service passes through Authelia first — if you’re not authenticated, you get a login page. If you are, the request passes through transparently.

Create the Authelia Configuration

mkdir -p ~/security-stack/authelia && cd ~/security-stack/authelia

Create configuration.yml:

# configuration.yml
server:
  address: 'tcp://0.0.0.0:9091'

log:
  level: info
  file_path: /config/authelia.log

totp:
  issuer: yourdomain.com

authentication_backend:
  file:
    path: /config/users_database.yml
    password:
      algorithm: argon2id
      iterations: 3
      memory: 65536
      parallelism: 4
      key_length: 32
      salt_length: 16

access_control:
  default_policy: deny
  rules:
    # Public services (no auth required)
    - domain: public.yourdomain.com
      policy: bypass

    # Services requiring login only
    - domain: jellyfin.yourdomain.com
      policy: one_factor

    # Sensitive services requiring MFA
    - domain:
        - portainer.yourdomain.com
        - proxmox.yourdomain.com
        - gitea.yourdomain.com
      policy: two_factor

session:
  name: authelia_session
  secret: generate-a-long-random-secret-here
  expiration: 12h
  inactivity: 45m
  domain: yourdomain.com

storage:
  local:
    path: /config/db.sqlite3

notifier:
  filesystem:
    filename: /config/notification.txt
  # For production, use SMTP:
  # smtp:
  #   host: smtp.gmail.com
  #   port: 587
  #   username: [email protected]
  #   password: app-password-here
  #   sender: [email protected]

Create the Users Database

nano users_database.yml
users:
  yourusername:
    displayname: "Your Name"
    password: "$argon2id$v=19$m=65536,t=3,p=4$generated-hash"
    email: [email protected]
    groups:
      - admins

Generate a password hash:

docker run --rm authelia/authelia:latest \
  authelia crypto hash generate argon2 \
  --password 'your-secure-password'

Add Authelia to Docker Compose

Append to your docker-compose.yml:

  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./authelia:/config
    environment:
      - TZ=America/New_York
    networks:
      - security
    labels:
      # If using Traefik:
      - "traefik.enable=true"
      - "traefik.http.routers.authelia.rule=Host(`auth.yourdomain.com`)"
      - "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
      - "traefik.http.services.authelia.loadbalancer.server.port=9091"

Configure Your Reverse Proxy for Forward Auth

For Nginx, add this to each protected server block:

location /authelia {
    internal;
    proxy_pass http://authelia:9091/api/verify;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Forwarded-For $remote_addr;
}

location / {
    auth_request /authelia;
    auth_request_set $user $upstream_http_remote_user;
    proxy_pass http://your-service:port;
}

For Traefik, add the forwardAuth middleware:

http:
  middlewares:
    authelia:
      forwardAuth:
        address: "http://authelia:9091/api/verify?rd=https://auth.yourdomain.com"
        trustForwardHeader: true
        authResponseHeaders:
          - Remote-User
          - Remote-Groups

Start Everything

cd ~/security-stack
docker compose up -d

Visit auth.yourdomain.com — you should see the Authelia login portal. Register your TOTP device and test that protected services redirect you to log in.

Connecting the Layers

The real power comes from integration. Here’s how to make all three tools work together.

Feed Authelia Logs to Fail2Ban and CrowdSec

Mount the Authelia log file so both tools can read it:

# In your CrowdSec service volumes:
volumes:
  - ./authelia/authelia.log:/var/log/authelia.log:ro

Install the Authelia parser for CrowdSec:

docker exec crowdsec cscli parsers install LePresworWorker/authelia-logs
docker exec crowdsec cscli scenarios install LePresworWorker/authelia-bf

Now both Fail2Ban and CrowdSec independently watch Authelia for brute-force attempts. Fail2Ban provides fast local banning. CrowdSec shares the attacker’s IP with the community.

Whitelist Your Local Network

Prevent locking yourself out. In Fail2Ban:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8

In CrowdSec, create a whitelist:

docker exec crowdsec cscli parsers install crowdsecurity/whitelists

Edit the whitelist config to include your LAN ranges.

In Authelia, use the bypass policy for local network access if desired:

access_control:
  rules:
    - domain: "*.yourdomain.com"
      networks:
        - 192.168.0.0/16
      policy: bypass

Testing the Stack

Test Fail2Ban

From a different IP (or using a VPN), deliberately fail SSH login 3+ times:

ssh wronguser@your-server-ip
# Enter wrong password 3 times

Check the ban:

sudo fail2ban-client status sshd
# Should show the IP in "Banned IP list"

Test CrowdSec

Check if community blocklists are active:

docker exec crowdsec cscli decisions list --origin CAPI

You should see IPs pre-banned from community intelligence.

Test Authelia

  1. Visit a protected service — you should be redirected to the Authelia login page
  2. Enter wrong credentials 3 times — Fail2Ban should ban your IP
  3. Log in correctly, then access another protected service — SSO should pass you through without re-authenticating

Monitoring and Maintenance

CrowdSec Dashboard

CrowdSec provides a free web dashboard at app.crowdsec.net showing:

  • Active decisions and alerts
  • Community blocklist coverage
  • Attack trends and geographic data

Useful Commands

# Fail2Ban: unban an IP
sudo fail2ban-client set sshd unbanip 1.2.3.4

# CrowdSec: manually ban an IP
docker exec crowdsec cscli decisions add --ip 1.2.3.4 --reason "manual ban"

# CrowdSec: remove a ban
docker exec crowdsec cscli decisions delete --ip 1.2.3.4

# Check all active bans across tools
sudo fail2ban-client status | grep "Jail list"
docker exec crowdsec cscli decisions list

Log Rotation

Make sure your logs don’t fill the disk. Add logrotate rules for Authelia:

sudo nano /etc/logrotate.d/authelia
/}pathdrcmn/aooiotitmstolapsi/ytrifaeeneusgmt7sophkteylia/authelia.log{

Troubleshooting

Locked yourself out? SSH in from a whitelisted IP and run:

sudo fail2ban-client unban --all
docker exec crowdsec cscli decisions delete --all

CrowdSec not detecting attacks? Check that log paths are correct and parsers match your log format:

docker exec crowdsec cscli parsers list
docker exec crowdsec tail -f /var/log/crowdsec.log

Authelia not redirecting? Verify your reverse proxy’s forwardAuth configuration. The most common issue is incorrect X-Forwarded-* headers. Check Authelia logs:

docker logs authelia

Services slow after adding Authelia? Each request triggers a verification call. Make sure Authelia is on the same Docker network as your reverse proxy to minimize latency.

What This Stack Doesn’t Cover

This is a strong foundation, but consider adding:

  • Cloudflare WAF or Tunnel — blocks attacks before they reach your server
  • WireGuard VPN — for services that should never be publicly exposed
  • Unattended upgrades — keep your OS patched automatically
  • OSSEC or Wazuh — host-based intrusion detection for deeper monitoring

Conclusion

Security is never a single tool — it’s layers. Fail2Ban gives you fast, proven log-based banning. CrowdSec adds community intelligence so you benefit from attacks blocked worldwide. Authelia ensures that even if an attacker slips past the network defenses, they still face a login wall with MFA.

Deploy all three and you’ve got a security stack that handles the vast majority of threats facing self-hosted infrastructure. The setup takes an afternoon, but the protection runs 24/7.

Start with Fail2Ban if you’re brand new. Add CrowdSec when you’re comfortable. Layer Authelia on top when you’re ready for proper access control. Each tool is valuable on its own — together, they’re a fortress.