--- created: 2026-03-13 updated: 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 - Working Traefik setup (see [[Traefik Multi-Stack Setup]]) - Router port forwarding for 80/443 only (remove 2222) --- ## 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` ```yaml 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 ```bash 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: ```bash # 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): ```bash 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: ```yaml 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: ```yaml 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: ```bash 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: ```bash mkdir -p /mnt/tank/stacks/traefik/crowdsec ``` Create `/mnt/tank/stacks/traefik/crowdsec/acquis.yaml`: ```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: ```yaml 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: ```bash 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: ```bash docker compose up -d ``` ### 3.4 Verify CrowdSec is Working ```bash # 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: ```yaml 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: ```yaml - "traefik.http.routers.service-name.middlewares=crowdsec@docker" ``` ### Multiple Middlewares Chain middlewares with commas: ```yaml - "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 ```bash # 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 ```bash # 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 ```bash # 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: ```bash # 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`: ```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: ```yaml 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 --- ## Related - [[Traefik Multi-Stack Setup]] - Base Traefik configuration - [[Security TODO]] - Other security improvements - [[Quick Reference]] - Common management commands