Files
Obsidian-Vault/Personal/Areas/Servers/TrueNAS/CrowdSec Setup.md
2026-03-13 20:01:15 +01:00

13 KiB

created, updated
created updated
2026-03-13 2026-03-13

CrowdSec Setup with Traefik

Overview

CrowdSec protects all services behind Traefik by analyzing access logs and blocking malicious IPs. Combined with disabling Gitea SSH (port 2222), all public traffic flows through Traefik where CrowdSec can inspect and block it.

Internet (Port 80/443 only)
    ↓
Router Port Forwarding
    ↓
┌──────────────────────────────────────────┐
│              Traefik                      │
│  ┌──────────────────────────────┐        │
│  │  CrowdSec Bouncer Middleware │ ← blocks│
│  └──────────────┬───────────────┘        │
│                 │ allowed traffic         │
└─────────────────┼────────────────────────┘
      traefik_proxy network
    ┌─────────┼─────────┐
    ↓         ↓         ↓
 Gitea    Servarr    Future
                    Services

CrowdSec Engine ← reads Traefik access logs
    ↓
Community Blocklists (optional)

Prerequisites


Step 1: Disable Gitea SSH and Switch to HTTPS Git

1.1 Close Port 2222

Remove the port 2222 forwarding rule from your router. This immediately stops all SSH brute-force attempts.

1.2 Remove SSH Port from Gitea Stack

Edit /mnt/tank/stacks/gitea/docker-compose.yml:

  • Remove the ports section (or just the 2222:22 mapping)
  • Optionally add GITEA__server__DISABLE_SSH=true
services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=sqlite3
      - GITEA__server__DOMAIN=git.vv.nl
      - GITEA__server__ROOT_URL=https://git.vv.nl
      - GITEA__server__DISABLE_SSH=true
      - GITEA__service__DISABLE_REGISTRATION=true
    networks:
      - traefik_proxy
      - gitea_internal
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    # No ports exposed - Traefik handles HTTPS, SSH disabled
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik_proxy"
      - "traefik.http.routers.gitea.rule=Host(`git.vv.nl`)"
      - "traefik.http.routers.gitea.entrypoints=websecure"
      - "traefik.http.routers.gitea.tls.certresolver=cloudflare"
      - "traefik.http.services.gitea.loadbalancer.server.port=3000"

1.3 Restart Gitea

cd /mnt/tank/stacks/gitea
docker compose down
docker compose up -d

1.4 Set Up HTTPS Git Authentication

On Gitea, create a personal access token:

  1. Go to https://git.vv.nl/user/settings/applications
  2. Create a new token with repository scope
  3. Save the token securely

On your local machines, configure git to use HTTPS:

# Update existing remotes from SSH to HTTPS
git remote set-url origin https://git.vv.nl/username/repo.git

# Store credentials so you don't have to enter the token every time
git config --global credential.helper store

# On first push/pull, use your Gitea username and the token as password
git push origin main
# Username: your-gitea-username
# Password: your-personal-access-token

Alternatively, use the token directly in the URL (less secure, stored in git config):

git remote set-url origin https://username:TOKEN@git.vv.nl/username/repo.git

Step 2: Enable Traefik Access Logs

CrowdSec needs Traefik's access logs to detect malicious behavior. Edit /mnt/tank/stacks/traefik/traefik.yml and add:

accessLog:
  filePath: "/var/log/traefik/access.log"
  bufferingSize: 100

# ... rest of your existing config (api, entryPoints, providers, certificatesResolvers)

Then update /mnt/tank/stacks/traefik/docker-compose.yml to mount the log directory:

services:
  traefik:
    # ... existing config ...
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
      - traefik-logs:/var/log/traefik    # Add this line

volumes:
  traefik-logs:
    name: traefik-logs    # Named volume, shared with CrowdSec

Restart Traefik:

cd /mnt/tank/stacks/traefik
docker compose down
docker compose up -d

Step 3: Deploy CrowdSec

Since CrowdSec is tightly coupled to Traefik (reads its logs, provides its middleware), it lives in the Traefik stack.

3.1 Create CrowdSec Acquis Config

Create the log acquisition config:

mkdir -p /mnt/tank/stacks/traefik/crowdsec

Create /mnt/tank/stacks/traefik/crowdsec/acquis.yaml:

filenames:
  - /var/log/traefik/*
labels:
  type: traefik

3.2 Add CrowdSec to Traefik Stack

Edit /mnt/tank/stacks/traefik/docker-compose.yml to add the CrowdSec engine and bouncer:

version: '3.8'

networks:
  traefik_proxy:
    name: traefik_proxy
    driver: bridge

volumes:
  traefik-logs:
    name: traefik-logs
  crowdsec-db:
    name: crowdsec-db
  crowdsec-config:
    name: crowdsec-config

services:
  traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: unless-stopped
    depends_on:
      - crowdsec-bouncer
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik_proxy
    ports:
      - "80:80"
      - "443:443"
    environment:
      - TZ=Europe/Amsterdam
      - CF_DNS_API_TOKEN=your-cloudflare-api-token
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
      - traefik-logs:/var/log/traefik
    labels:
      - "traefik.enable=true"
      # Dashboard
      - "traefik.http.routers.traefik.rule=Host(`traefik.vv.nl`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=cloudflare"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.middlewares=traefik-auth,crowdsec@docker"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"

  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    networks:
      - traefik_proxy
    environment:
      - TZ=Europe/Amsterdam
      # Install the Traefik log parser and HTTP scenarios
      - COLLECTIONS=crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/base-http-scenarios
      # Enroll with CrowdSec Console (optional, get key from app.crowdsec.net)
      # - ENROLL_KEY=your-enrollment-key
    volumes:
      - crowdsec-db:/var/lib/crowdsec/data
      - crowdsec-config:/etc/crowdsec
      - traefik-logs:/var/log/traefik:ro    # Read Traefik logs
      - ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro

  crowdsec-bouncer:
    image: fbonalair/traefik-crowdsec-bouncer:latest
    container_name: crowdsec-bouncer
    restart: unless-stopped
    depends_on:
      - crowdsec
    networks:
      - traefik_proxy
    environment:
      - CROWDSEC_BOUNCER_API_KEY=GENERATE_THIS    # See step 3.3
      - CROWDSEC_AGENT_HOST=crowdsec:8080
      - GIN_MODE=release
    labels:
      - "traefik.enable=true"
      # Define the CrowdSec middleware (forwardAuth to the bouncer)
      - "traefik.http.middlewares.crowdsec.forwardauth.address=http://crowdsec-bouncer:8080/api/v1/forwardAuth"
      - "traefik.http.middlewares.crowdsec.forwardauth.trustForwardHeader=true"

3.3 Generate the Bouncer API Key

First, bring up CrowdSec alone to generate the API key:

cd /mnt/tank/stacks/traefik

# Start only CrowdSec first
docker compose up -d crowdsec

# Wait a few seconds for it to initialize, then generate a bouncer API key
docker exec crowdsec cscli bouncers add traefik-bouncer

# Copy the generated API key and put it in the docker-compose.yml
# Replace GENERATE_THIS with the actual key

Then start everything:

docker compose up -d

3.4 Verify CrowdSec is Working

# Check CrowdSec is parsing logs
docker exec crowdsec cscli metrics

# Check installed collections
docker exec crowdsec cscli collections list

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

# Check bouncer is registered
docker exec crowdsec cscli bouncers list

Step 4: Apply CrowdSec Middleware to All Services

For each service behind Traefik, add crowdsec@docker to its middleware chain.

Gitea

In /mnt/tank/stacks/gitea/docker-compose.yml, add the middleware label:

labels:
  - "traefik.enable=true"
  - "traefik.docker.network=traefik_proxy"
  - "traefik.http.routers.gitea.rule=Host(`git.vv.nl`)"
  - "traefik.http.routers.gitea.entrypoints=websecure"
  - "traefik.http.routers.gitea.tls.certresolver=cloudflare"
  - "traefik.http.routers.gitea.middlewares=crowdsec@docker"    # Add this
  - "traefik.http.services.gitea.loadbalancer.server.port=3000"

Any Future Service

Same pattern - add crowdsec@docker to the middlewares label:

- "traefik.http.routers.service-name.middlewares=crowdsec@docker"

Multiple Middlewares

Chain middlewares with commas:

- "traefik.http.routers.traefik.middlewares=traefik-auth,crowdsec@docker"

Step 5: Optional - Enroll in CrowdSec Console

The CrowdSec Console at app.crowdsec.net provides:

  • Community blocklists (block known-bad IPs before they hit your server)
  • Dashboard to visualize attacks
  • Alert notifications

To enroll:

  1. Create a free account at https://app.crowdsec.net
  2. Get your enrollment key from the console
  3. Add to docker-compose.yml: ENROLL_KEY=your-key
  4. Or enroll manually: docker exec crowdsec cscli console enroll your-key

Management Commands

# View current bans
docker exec crowdsec cscli decisions list

# Manually ban an IP
docker exec crowdsec cscli decisions add --ip 176.120.22.17 --reason "SSH brute force"

# Unban an IP (if you accidentally ban yourself)
docker exec crowdsec cscli decisions delete --ip YOUR_IP

# View metrics (parsed logs, scenarios triggered)
docker exec crowdsec cscli metrics

# View alerts
docker exec crowdsec cscli alerts list

# Update CrowdSec hub (scenarios, parsers)
docker exec crowdsec cscli hub update
docker exec crowdsec cscli hub upgrade

Troubleshooting

Bouncer Not Blocking

# Verify bouncer is registered
docker exec crowdsec cscli bouncers list

# Check bouncer logs
docker logs crowdsec-bouncer

# Test with a manual ban on a test IP
docker exec crowdsec cscli decisions add --ip 1.2.3.4 --reason "test"
# Then try to access your services from that IP
docker exec crowdsec cscli decisions delete --ip 1.2.3.4

CrowdSec Not Parsing Logs

# Check if acquisition is working
docker exec crowdsec cscli metrics

# Look for "traefik" in the acquisition metrics
# If zero lines parsed, check the log path and acquis.yaml

# Verify Traefik is writing logs
docker exec traefik ls -la /var/log/traefik/

# Check CrowdSec logs for errors
docker logs crowdsec

Locked Out of Your Own Services

If you accidentally ban your own IP:

# SSH into TrueNAS (SSH is on the host, not through Traefik)
ssh user@truenas-local-ip

# Remove the ban
docker exec crowdsec cscli decisions delete --ip YOUR_IP

# Whitelist your IP to prevent future bans
docker exec crowdsec cscli parsers install crowdsecurity/whitelists
# Edit the whitelist config
docker exec -it crowdsec vi /etc/crowdsec/parsers/s02-enrich/whitelists.yaml

To permanently whitelist your IP, create /mnt/tank/stacks/traefik/crowdsec/whitelist.yaml:

name: mywhitelists
description: "My personal whitelist"
whitelist:
  reason: "My home/office IP"
  ip:
    - "YOUR_HOME_IP"
    # Add any other trusted IPs

Mount it in the CrowdSec container:

volumes:
  - ./crowdsec/whitelist.yaml:/etc/crowdsec/parsers/s02-enrich/mywhitelists.yaml:ro

Port Forwarding (Updated)

After this setup, your router should only forward:

External Port Internal Port Protocol Service
80 80 TCP Traefik HTTP (redirects to HTTPS)
443 443 TCP Traefik HTTPS (CrowdSec protected)

Port 2222 is no longer forwarded.


Architecture Summary

  • All public traffic enters through ports 80/443 only
  • Traefik terminates TLS and routes requests
  • CrowdSec bouncer middleware checks every request against the ban list
  • CrowdSec engine continuously parses Traefik access logs for malicious patterns
  • Community blocklists proactively block known-bad IPs
  • No direct port exposure for any backend service