If you use Cloudflare:
Make sure to set SSL/TLS to FULL
cd /home/myusername/docker
mkdir traefik-crowdsec && cd "$_"
mkdir traefik-data && cd "$_"
touch acme.json && chmod 600 acme.json
global:
checkNewVersion: true
sendAnonymousUsage: false
serversTransport:
insecureSkipVerify: true
entryPoints:
# Not used in apps, but redirect everything from HTTP to HTTPS
http:
address: :80
forwardedHeaders:
trustedIPs: &trustedIps
# Start of Clouflare public IP list for HTTP requests, remove this if you don't use it
- 173.245.48.0/20
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 141.101.64.0/18
- 108.162.192.0/18
- 190.93.240.0/20
- 188.114.96.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 162.158.0.0/15
- 104.16.0.0/12
- 172.64.0.0/13
- 131.0.72.0/22
- 2400:cb00::/32
- 2606:4700::/32
- 2803:f800::/32
- 2405:b500::/32
- 2405:8100::/32
- 2a06:98c0::/29
- 2c0f:f248::/32
# End of Cloudlare public IP list
http:
redirections:
entryPoint:
to: https
scheme: https
# HTTPS endpoint, with domain wildcard
https:
address: :443
forwardedHeaders:
# Reuse list of Cloudflare Trusted IP's above for HTTPS requests
trustedIPs: *trustedIps
http:
tls:
# Generate a wildcard domain certificate
certResolver: cloudflare
domains:
# - main: "local.DOMAIN.COM" # uncomment to enable certs for internal domain DNS
# sans: # uncomment to enable certs for internal domain DNS
# - "*.local.DOMAIN.COM" # uncomment to enable certs for internal domain DNS
- main: "DOMAIN.COM"
sans:
- "*.DOMAIN.COM"
middlewares:
- securityHeaders@file
- crowdsec-bouncer@file
- gzip@file
- cloudflarewarp@file
metrics:
address: ":8083"
providers:
providersThrottleDuration: 2s
# File provider for connecting things that are outside of docker / defining middleware
file:
filename: /etc/traefik/fileConfig.yml
watch: true
# Docker provider for connecting all apps that are inside of the docker network
docker:
watch: true
network: proxy # Add Your Docker Network Name Here
# Default host rule to containername.domain.example
defaultRule: "Host(`{{ index .Labels \"com.docker.compose.service\"}}.DOMAIN.COM`)"
exposedByDefault: false
endpoint: "unix:///var/run/docker.sock"
# Enable traefik ui
api:
dashboard: false # Set to true to enable dashboard, not recommended for production
insecure: false # Set to true to enable insecure dashboard, not recommended for production
debug: false # Set to true to enable debug mode, not recommended for production
metrics:
prometheus:
addEntryPointsLabels: true
addRoutersLabels: true
addServicesLabels: true
entryPoint: metrics
# :8083/ping
ping:
entryPoint: metrics
log:
level: INFO # DEBUG, INFO, WARN, ERROR, FATAL, and PANIC
filePath: "/logs/traefik.log"
format: json
accessLog:
filePath: "/var/log/crowdsec/traefik_access.log"
bufferingSize: 50
format: json
filters:
statusCodes:
- "300-302"
- "400-599" # error-pages-mw range
retryAttempts: true
minDuration: "10ms"
# Use letsencrypt to generate ssl serficiates
certificatesResolvers:
cloudflare:
acme:
email: [email protected] # change to your provider account email address.
storage: /etc/traefik/acme.json
caServer: https://acme-v02.api.letsencrypt.org/directory # prod (default)
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
# Plugins (optional)
experimental:
plugins:
cloudflarewarp:
moduleName: "github.com/BetterCorp/cloudflarewarp"
version: "v1.3.3"
# CrowdSec AppSec WAF Plugin
crowdsec-bouncer:
moduleName: "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
version: "v1.4.4"
fileConfig.yml under the middlewares: section middlewares:
# Crowdsec bouncer & AppSec WAF
crowdsec-bouncer:
plugin:
crowdsec-bouncer:
enabled: true
crowdsecMode: stream
crowdsecLapiHost: crowdsec:8080
crowdsecLapiScheme: http
crowdsecLapiKey: "YOUR_SUPER_SECURE_CROWDSEC_BOUNCER_API_TOKEN" # Replace in configuration phase
# AppSec WAF Settings
crowdsecAppsecEnabled: true
crowdsecAppsecHost: crowdsec:7422
crowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
cd ..
mkdir crowdsec-logs && cd "$_"
nano acquis.yaml
filenames:
- /var/log/crowdsec/traefik.log
labels:
type: traefik
---
filenames:
- /var/log/auth.log
labels:
type: syslog
cd ..
mkdir crowdsec-config && cd "$_"
touch acquis.yaml && chmod 600 acquis.yaml
This file configures the Web Application Firewall (WAF). It includes Layer 1 (Virtual Patching/Immediate Blocking) and Layer 2 (OWASP Core Rule Set Out-of-Band detection).
appsec_configs:
- crowdsecurity/appsec-default # Virtual patching rules (in-band blocking)
- crowdsecurity/crs # OWASP CRS rules (out-of-band detection)
labels:
type: appsec
listen_addr: 0.0.0.0:7422
source: appsec
cd ..
services:
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: traefik_crowdsec
restart: unless-stopped
environment:
GID: "${GID-1000}"
# Installs Linux, Traefik, WAF Virtual Patching, Generic Rules, and OWASP CRS
COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules crowdsecurity/appsec-crs"
volumes:
- ./crowdsec-config/acquis.yaml:/etc/crowdsec/acquis.yaml
- ./crowdsec-config/appsec.yaml:/etc/crowdsec/acquis.d/appsec.yaml
- ./crowdsec-config:/etc/crowdsec/
- ./crowdsec-db:/var/lib/crowdsec/data/
ports:
- 6060:6060
networks:
- proxy
depends_on:
- traefik
traefik:
image: traefik:latest
container_name: traefik
hostname: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- 80:80 # local http
- 443:443 # local https
volumes:
- /etc/localtime:/etc/localtime:ro
- ./traefik-data:/etc/traefik
- /var/run/docker.sock:/var/run/docker.sock
environment:
CF_API_EMAIL: [email protected]
CF_DNS_API_TOKEN: YOUR_SUPER_SECURE_CLOUDFLARE_API_TOKEN
networks:
- proxy
error-pages:
image: ghcr.io/tarampampam/error-pages:latest
container_name: error-pages
environment:
TEMPLATE_NAME: connection # Choose a template (e.g., l7, cats, ghost)
SHOW_DETAILS: true # Optional: Show request details in error pages
SEND_SAME_HTTP_CODE: true # Return the same HTTP status code as the error
restart: unless-stopped
depends_on:
- traefik
networks:
- proxy
networks:
proxy:
driver: bridge
external: true
docker compose up -d
docker exec traefik_crowdsec cscli bouncers add bouncer-traefik
fileConfig.yml middleware under: crowdsecLapiKey: "YOUR_SUPER_SECURE_CROWDSEC_BOUNCER_API_TOKEN"Use the template Edit zone DNS.
Add the following settings
Then click Continue to Summary and then Create Token.
docker-compose.yml at YOUR_SUPER_SECURE_CLOUDFLARE_API_TOKEN# Restart the stack to apply all API keys
docker compose down
docker compose up -d
Visit the Cloudflare DNS records and use the following setup:
A traefik YOUR_PUBLIC_IP Proxied Auto
CNAME Records (pointing to Traefik domain):
CNAME myservice traefik.DOMAIN.COM Proxied Auto
CNAME myservice02 traefik.DOMAIN.COM Proxied Auto
With the new AppSec WAF and CRS (Core Rule Set) running, your protection works in two layers:
Tests the WAF's ability to instantly block known malicious files.
curl -I https://DOMAIN.COM/.env
Expected Result: You should be immediately blocked with an HTTP 403 Forbidden.
Tests the WAF's ability to analyze generic attacks silently and ban repeat offenders.
curl "https://DOMAIN.COM/?id=1'+OR+'1'='1"
Check if the WAF recorded the attack:
docker exec traefik_crowdsec cscli alerts list
Expected Result: You should see a new alert for your IP showing anomaly score out-of-band: sql_injection.
Simulate a persistent attacker to trigger the crowdsec-appsec-outofband scenario:
for i in {1..6}; do curl "https://DOMAIN.COM/?id=1'+OR+'1'='1"; done
Check the alerts again:
docker exec traefik_crowdsec cscli alerts list
Expected Result: You will now see a ban:1 decision. That IP is entirely blocked from accessing any service on your Traefik instance.
Ban an IP Manually:
docker exec traefik_crowdsec cscli decisions add --ip 192.168.123.123 --duration 24h --reason "web bruteforce"
List existing Bans:
docker exec traefik_crowdsec cscli decisions list
Whitelist / Remove an IP Ban:
docker exec traefik_crowdsec cscli decisions delete --ip 192.168.123.123
cd /home/myusername/docker/traefik-crowdsec/crowdsec-config
nano profiles.yaml
Uncomment (remove the hashtag) the following section:
#notifications:
# - http_default
cd notifications
nano http.yaml
type: http # Don't change
name: http_default # Must match the registered plugin in the profile
# One of "trace", "debug", "info", "warn", "error", "off"
log_level: info
# group_wait: # Time to wait collecting alerts before relaying a message to this plugin, eg "30s"
# group_threshold: # Amount of alerts that triggers a message before <group_wait> has expired, eg "10"
# max_retry: # Number of attempts to relay messages to plugins in case of error
# timeout: # Time to wait for response from the plugin before considering the attempt a failure, eg "10s"
#-------------------------
# plugin-specific options
# The following template receives a list of models.Alert objects
# The output goes in the http request body
format: |
{{ range . -}}
{{ $alert := . -}}
{
"extras": {
"client::display": {
"contentType": "text/markdown"
}
},
"priority": 3,
{{range .Decisions -}}
"title": "{{.Type }} {{ .Value }} for {{.Duration}}",
"message": "{{.Scenario}} \n\n[crowdsec cti](https://app.crowdsec.net/cti/{{.Value -}}) \n\n[shodan](https://shodan.io/host/{{.Value -}})"
{{end -}}
}
{{ end -}}
# The plugin will make requests to this url, eg: https://www.example.com/
url: http://IMPORT_GOTFIY_URL_HERE/message
# Any of the http verbs: "POST", "GET", "PUT"...
method: POST
headers:
X-Gotify-Key: IMPORT_CROWDSEC_TOKEN_HERE
Content-Type: application/json
# skip_tls_verification: # true or false. Default is false
CrowdSechttp.yaml and replace the following:IMPORT_CROWDSEC_TOKEN_HERE replace with the Gotify token that you copiedIMPORT_GOTFIY_URL_HERE replace with the Gotify url that you visit to access the GUIdocker restart traefik_crowdsec
The following command will send a test notification from CrowdSec to Gotify:
docker exec traefik_crowdsec cscli notifications test http_default