Webmail Website Panel VPS Panel Client Panel
Client Services
Tutorials

Self-Hosted Services with CrowdSec and Traefik

Protect Self-Hosted Services with CrowdSec and Traefik.

Self-Hosted Services with CrowdSec and Traefik

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

  • 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

AI
AI
CLOUD HIVE DC AI
Welcome aboard!
Ask me anything about CLOUD HIVE DC services. 🚀
I'm still learning, so please be patient with me 😊😋
👨‍💻 An operator has joined the chat