Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Protect Self-Hosted Services with CrowdSec and Traefik

profile picture
Author
Yusuf Khasbulatov
Published
2026-01-23
Time to read
9 minutes reading time

About the author- DevOps engineer focused on self-hosted infrastructure and security hardening

Introduction

CrowdSec is an open-source, collaborative intrusion detection system that analyzes logs, detects attacks, and shares threat intelligence with the community. Combined with Traefik, it provides real-time protection against:

  • Brute-force attacks (SSH, HTTP authentication, database)
  • Web application attacks (SQL injection, XSS, path traversal)
  • Vulnerability scanners and automated bots
  • Known malicious IPs from the CrowdSec community blocklist

This tutorial covers:

  1. Deploying CrowdSec as a Docker container alongside Coolify
  2. Integrating CrowdSec with Traefik to block malicious HTTP requests
  3. Installing the firewall bouncer to protect SSH and other services
  4. Creating a custom parser for Supabase Supavisor to detect database brute-force attacks

Prerequisites

  • A server running Coolify with Traefik proxy
  • SSH access to a server you want to monitor (e.g. a Supabase server)
  • Basic familiarity with Docker and YAML

Step 1 - Deploy CrowdSec Container

Step 1.1 - Create directory structure

SSH into your server (e.g. Supabase server) and create the CrowdSec directories:

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

Step 1.2 - Create Docker Compose file

cat > /opt/crowdsec/docker-compose.yml << 'EOF'
services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    ports:
      - "127.0.0.1:8180:8080"  # LAPI for bouncers (localhost only)
      - "0.0.0.0:6060:6060"    # Metrics (restrict via UFW)
    security_opt:
      - no-new-privileges:true
    environment:
      - GID=1000
      - COLLECTIONS=crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors crowdsecurity/base-http-scenarios crowdsecurity/sshd crowdsecurity/linux
    volumes:
      # CrowdSec data and config
      - /opt/crowdsec/data:/var/lib/crowdsec/data
      - /opt/crowdsec/config:/etc/crowdsec
      # Log sources to analyze
      - /var/log/traefik:/var/log/traefik:ro
      - /var/log/auth.log:/var/log/auth.log:ro
      - /var/log/syslog:/var/log/syslog:ro
      # Docker socket for container log acquisition
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - coolify

networks:
  coolify:
    external: true
EOF

Step 1.3 - Start CrowdSec

cd /opt/crowdsec
docker compose up -d

Wait approximately 30 seconds for CrowdSec to initialize and download collections.

Step 2 - Configure Log Acquisition

CrowdSec needs to know which logs to analyze and how to parse them.

Step 2.1 - Configure log sources

cat > /opt/crowdsec/config/acquis.yaml << 'EOF'
---
filenames:
  - /var/log/auth.log
  - /var/log/syslog
labels:
  type: syslog
---
filenames:
  - /var/log/traefik/*.log
labels:
  type: traefik
---
# Supavisor logs (Docker socket acquisition)
# Uses regex to match Coolify's dynamic container naming
source: docker
container_name_regexp:
  - "supabase-supavisor-.*"
labels:
  type: supavisor
EOF

Step 2.2 - Restart CrowdSec

docker restart crowdsec

Step 3 - Create Custom Supavisor Parser

Supavisor (Supabase's connection pooler) logs authentication failures, but CrowdSec doesn't have a built-in parser for it. Let's create one.

Step 3.1 - Create the parser

cat > /opt/crowdsec/config/parsers/s01-parse/supavisor-logs.yaml << 'EOF'
name: crowdsecurity/supavisor-logs
description: "Parse Supavisor connection pooler logs for authentication failures"
filter: "evt.Parsed.program == 'supavisor'"
onsuccess: next_stage
debug: false

# Supavisor uses Elixir Logger format with metadata
# Example log:
# 18:38:17.778 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=123.123.123.123 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query

pattern_syntax:
  SUPAVISOR_TS: '%{TIME:timestamp}\.%{INT:timestamp_ms}'
  SUPAVISOR_LEVEL: '\[%{WORD:log_level}\]'
  SUPAVISOR_META_FULL: 'project=%{DATA:project}\s+user=%{DATA:db_user}\s+region=%{DATA:region}\s+mode=%{DATA:pool_mode}\s+type=%{DATA:pool_type}\s+app_name=%{DATA:app_name}\s+peer_ip=%{IP:source_ip}'
  SUPAVISOR_META_PARTIAL: "region=%{DATA:region}"

nodes:
  # Pattern 1: Wrong password authentication failure
  - grok:
      pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+Exchange error:\s+"Wrong password"%{GREEDYDATA}'
      apply_on: Line.Raw
    statics:
      - meta: log_type
        value: supavisor_auth_fail
      - meta: service
        value: supavisor
      - meta: source_ip
        expression: evt.Parsed.source_ip

  # Pattern 2: SSL required error
  - grok:
      pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+Tenant is not allowed to connect without SSL%{GREEDYDATA}'
      apply_on: Line.Raw
    statics:
      - meta: log_type
        value: supavisor_ssl_required
      - meta: service
        value: supavisor
      - meta: source_ip
        expression: evt.Parsed.source_ip

  # Pattern 3: Generic exchange error with peer_ip
  - grok:
      pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+Exchange error:%{GREEDYDATA:error_detail}'
      apply_on: Line.Raw
    statics:
      - meta: log_type
        value: supavisor_auth_fail
      - meta: service
        value: supavisor
      - meta: source_ip
        expression: evt.Parsed.source_ip

  # Pattern 4: Any error with peer_ip (fallback)
  - grok:
      pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+%{GREEDYDATA:error_message}'
      apply_on: Line.Raw
    filter: "evt.Parsed.log_level == 'error'"
    statics:
      - meta: log_type
        value: supavisor_error_with_ip
      - meta: service
        value: supavisor
      - meta: source_ip
        expression: evt.Parsed.source_ip

statics:
  - meta: service
    value: supavisor
EOF

Step 3.2 - Create the brute-force scenario

cat > /opt/crowdsec/config/scenarios/supavisor-bf.yaml << 'EOF'
type: leaky
name: crowdsecurity/supavisor-bf
description: "Detect brute force attacks against PostgreSQL via Supavisor connection pooler"
filter: evt.Meta.log_type == 'supavisor_auth_fail'
groupby: evt.Meta.source_ip
capacity: 5
leakspeed: 30s
blackhole: 5m
labels:
  service: supavisor
  confidence: 3
  spoofable: 0
  classification:
    - attack.T1110
  behavior: "database:bruteforce"
  label: "Supavisor bruteforce"
  remediation: true
EOF

This scenario triggers a ban when an IP makes 5 failed authentication attempts within 30 seconds.

Step 3.3 - Restart CrowdSec

docker restart crowdsec

Step 4 - Integrate CrowdSec with Traefik

The Traefik bouncer plugin checks incoming requests against CrowdSec's decision database and blocks banned IPs.

Step 4.1 - Generate bouncer API key

docker exec crowdsec cscli bouncers add traefik-bouncer

Save this API key — you'll need it in the next step.

Example output:

API key for 'traefik-bouncer':

   aBcDeFgHiJkLmNoPqRsTuVwXyZ123456

Please keep this key since you will not be able to retrieve it!

Step 4.2 - Update Traefik configuration in Coolify

In Coolify → ServerProxy tab, update the Traefik configuration.

Add the CrowdSec plugin to commands:

    command:
      # ... existing commands ...
      - "--accesslog=true"
      - "--accesslog.filepath=/var/log/traefik/access.log"
      - "--accesslog.format=json"
      - "--accesslog.fields.headers.defaultmode=drop"
      - "--accesslog.fields.headers.names.User-Agent=keep"
      # CrowdSec Bouncer Plugin
      - "--experimental.plugins.crowdsec.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
      - "--experimental.plugins.crowdsec.version=v1.4.6"

Step 4.3 - Create CrowdSec middleware configuration

On your server (e.g. Supabase server):

Replace <YOUR-BOUNCER-API-KEY> with your actual key.

cat > /data/coolify/proxy/dynamic/crowdsec.yml << 'EOF'
http:
  middlewares:
    crowdsec:
      plugin:
        crowdsec:
          enabled: true
          crowdsecMode: live
          crowdsecLapiKey: "<YOUR-BOUNCER-API-KEY>"
          crowdsecLapiHost: "crowdsec:8080"
          crowdsecLapiScheme: http
          forwardedHeadersTrustedIPs:
            - 10.0.0.0/8
            - 172.16.0.0/12
            - 192.168.0.0/16
          clientTrustedIPs:
            - 10.0.0.0/8
            - 172.16.0.0/12
            - 192.168.0.0/16
EOF

Note: Traefik automatically watches the /data/coolify/proxy/dynamic/ directory and loads changes without restart.

Step 4.4 - Enable middleware on HTTPS entrypoint

In Coolify → ServerProxy → labels section:

    labels:
      # ... existing labels ...
      - traefik.http.routers.traefik.middlewares=crowdsec@file

Step 4.5 - Save and restart

Click Save and then Restart Proxy.

Step 5 - Install Firewall Bouncer

The Traefik bouncer only protects HTTP traffic. To protect SSH and database ports, install the firewall bouncer.

Step 5.1 - Add CrowdSec repository

curl -s https://install.crowdsec.net | sudo sh

Step 5.2 - Install nftables bouncer

apt install crowdsec-firewall-bouncer-nftables -y

Step 5.3 - Generate API key

docker exec crowdsec cscli bouncers add firewall-bouncer

Step 5.4 - Configure the bouncer

Replace <YOUR-FIREWALL-BOUNCER-API-KEY> with your actual key.

cat > /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml << 'EOF'
mode: nftables
pid_dir: /var/run/
update_frequency: 10s
daemonize: true
log_mode: file
log_dir: /var/log/
log_level: info
log_compression: true
log_max_size: 100
log_max_backups: 3
log_max_age: 30
api_url: http://127.0.0.1:8180/
api_key: <YOUR-FIREWALL-BOUNCER-API-KEY>
insecure_skip_verify: false
disable_ipv6: false
deny_action: DROP
deny_log: false
supported_decisions_types:
  - ban
nftables:
  ipv4:
    enabled: true
    set-only: false
    table: crowdsec
    chain: crowdsec-chain
    priority: -10
  ipv6:
    enabled: true
    set-only: false
    table: crowdsec6
    chain: crowdsec6-chain
    priority: -10
nftables_hooks:
  - input
  - forward
EOF

Step 5.5 - Enable and start

systemctl enable crowdsec-firewall-bouncer
systemctl start crowdsec-firewall-bouncer

Step 6 - Configure Log Rotation

Prevent Traefik logs from filling the disk:

cat > /etc/logrotate.d/traefik << 'EOF'
/var/log/traefik/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 0644 root root
    postrotate
        docker kill --signal="USR1" coolify-proxy 2>/dev/null || true
    endscript
}
EOF

Step 7 - Verify CrowdSec Status

Step 7.1 - Check container status

docker ps | grep crowdsec

Step 7.2 - View metrics

docker exec crowdsec cscli metrics

Step 7.3 - Check registered bouncers

docker exec crowdsec cscli bouncers list

Expected output showing both bouncers:

Name              IP Address  Valid  Last API pull         Type    Version
traefik-bouncer   172.x.x.x   :heavy_check_mark:     2024-01-01T12:00:00Z  Go-http
firewall-bouncer  127.0.0.1   :heavy_check_mark:     2024-01-01T12:00:00Z  Go-http

Step 7.4 - View active decisions (bans)

docker exec crowdsec cscli decisions list

Step 7.5 - Check firewall bouncer status

systemctl status crowdsec-firewall-bouncer

Step 8 - Test the Setup

Step 8.1 - Manually add a test ban

Ban a test IP temporarily:

docker exec crowdsec cscli decisions add --ip 192.0.2.1 --duration 5m --type ban

Step 8.2 - Verify the ban

docker exec crowdsec cscli decisions list

Step 8.3 - Remove the test ban

docker exec crowdsec cscli decisions delete --ip 192.0.2.1

Quick Reference - CrowdSec Commands

Command Description
docker exec crowdsec cscli metrics View detection metrics
docker exec crowdsec cscli decisions list List active bans
docker exec crowdsec cscli decisions delete --ip X.X.X.X Unban an IP
docker exec crowdsec cscli alerts list View recent alerts
docker exec crowdsec cscli bouncers list List registered bouncers
docker exec crowdsec cscli collections list List installed collections
docker exec crowdsec cscli hub update Update threat definitions
docker exec crowdsec cscli explain --log "..." --type traefik Debug log parsing

Conclusion

Your server now has comprehensive intrusion detection and prevention:

  • ✅ HTTP traffic protected via Traefik bouncer
  • ✅ SSH and direct connections protected via firewall bouncer
  • ✅ Automatic detection of brute-force attacks, vulnerability scans, and web exploits
  • ✅ Custom detection for Supabase Supavisor database attacks
  • ✅ Participation in CrowdSec's community threat intelligence network

Next steps:

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2026 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more