Traefik with CrowdSec Security Engine on Ubuntu 24.04 on Azure User Guide
Overview
Traefik with CrowdSec Security Engine is an edge appliance that terminates inbound HTTP and HTTPS traffic, routes it to your backend services, and defends it at the same time. Traefik handles routing, load balancing and automatic Let's Encrypt certificates. The CrowdSec Security Engine reads Traefik's own access log, recognises probing, crawling, brute force and known CVE exploitation patterns through its behavioural scenarios, and records a ban decision. A bouncer middleware inside Traefik enforces those decisions on the very next request, so an attacking source is refused at the edge instead of reaching your application.
The important part is that these two halves are already wired together and proven. Detection and enforcement both run on the instance against local decisions, with no external service required and no account to create.
What is included:
- Traefik 3.7.10 (a single static Go binary) at
/usr/local/bin/traefik - CrowdSec Security Engine 1.7.x with its Local API on
127.0.0.1:8080, never exposed publicly - The CrowdSec bouncer as a Traefik middleware, vendored on disk so it loads with no internet access
- The
crowdsecurity/traefikcollection, which parses Traefik's access log, plus thelinuxandsshdcollections web(:80),websecure(:443) and a loopbackping(127.0.0.1:8082) entrypoint- The Traefik dashboard at
/dashboard/behind the bouncer and HTTP basic auth (api.insecureisfalse) - A branded block page shown to refused visitors, at
/etc/traefik/ban.html - A pre-configured Let's Encrypt certificate resolver (HTTP challenge)
- A dedicated Azure data disk at
/var/lib/traefikfor ACME storage and logs, re-provisioned with every VM - File-provider dynamic configuration in
/etc/traefik/dynamic/for your own routers and services - Unique per-VM credentials generated on first boot, and a start-up guard that refuses to run on a placeholder credential
- 24/7 cloudimg support
How the protection works
Requests flow through a closed loop:
visitor --> Traefik (:80/:443) --> [crowdsec bouncer] --> your backend
| ^
| writes JSON access log | checks local decisions
v |
/var/lib/traefik/access.log |
| |
+--> CrowdSec parses it -+
scenarios fire, a ban decision is stored locally
Because CrowdSec's input is Traefik's own access log, anything you publish through this proxy is protected automatically. You do not have to instrument your application.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet and subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is a good starting point; scale up for high request volumes. NSG inbound: allow 22/tcp from your management network, and 80/tcp and 443/tcp for proxied traffic and the dashboard. The CrowdSec Local API on 8080 is bound to loopback and must never be opened. For real Let's Encrypt certificates, point a DNS name at the VM's public IP.
Step 1 - Deploy from the Azure Marketplace
In the Azure portal choose Create a resource, search for Traefik with CrowdSec Security Engine on Ubuntu 24.04 LTS by cloudimg, and select Create. Pick your subscription, resource group and region, choose the Standard_B2ms size, select SSH public key authentication with the username azureuser, and allow inbound 22, 80 and 443. Review and create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group my-resource-group \
--name traefik-crowdsec-01 \
--image cloudimg:traefik-crowdsec:default:latest \
--size Standard_B2ms \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Then open the ports the proxy needs:
az vm open-port --resource-group my-resource-group --name traefik-crowdsec-01 --port 80 --priority 1010
az vm open-port --resource-group my-resource-group --name traefik-crowdsec-01 --port 443 --priority 1020
Step 3 - Connect to your VM
ssh azureuser@YOUR_VM_PUBLIC_IP
Step 4 - Confirm the services are running
The appliance runs three units: Traefik, the CrowdSec engine, and the small loopback backend that serves the default landing page.
sudo systemctl is-active traefik.service crowdsec.service cloudimg-landing.service
Expected output:
active
active
active
Check the versions:
/usr/local/bin/traefik version | head -3
dpkg-query -W -f='crowdsec ${Version}\n' crowdsec

Step 5 - Retrieve your per-VM credentials
Every VM generates its own credentials on first boot. Nothing is shared between deployments.
sudo cat /root/traefik-crowdsec-credentials.txt
This file contains your dashboard URL, the admin username, and the password unique to this VM. Store the password in your password manager, then treat this file as sensitive (it is 0600 root:root).
Step 6 - Check the health endpoints
Traefik exposes a loopback ping endpoint, and CrowdSec exposes its Local API health endpoint. Both are bound to 127.0.0.1 and are not reachable from outside the VM.
curl -s -o /dev/null -w 'traefik ping: %{http_code}\n' http://127.0.0.1:8082/ping
curl -s http://127.0.0.1:8080/health; echo
Expected output:
traefik ping: 200
{"status":"up"}
Step 7 - Open the dashboard
Browse to http://YOUR_VM_PUBLIC_IP/ and you will see the default landing page, served through the CrowdSec bouncer.

Now browse to http://YOUR_VM_PUBLIC_IP/dashboard/ and sign in as admin with the password from Step 5. The dashboard shows your entrypoints, routers, services and middlewares.

The dashboard is never exposed unauthenticated. You can confirm that from the command line:
PW=$(sudo grep '^TRAEFIK_ADMIN_PASSWORD=' /root/traefik-crowdsec-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'no credentials: %{http_code}\n' http://127.0.0.1/dashboard/
curl -s -o /dev/null -w 'with credentials: %{http_code}\n' -u "admin:$PW" http://127.0.0.1/dashboard/
Expected output:
no credentials: 401
with credentials: 200
Step 8 - Confirm the bouncer middleware is active
Open HTTP Middlewares in the dashboard. You should see crowdsec@file with a green status. This is the component that enforces bans.

The same check from the command line:
PW=$(sudo grep '^TRAEFIK_ADMIN_PASSWORD=' /root/traefik-crowdsec-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" http://127.0.0.1/api/http/middlewares \
| jq -r '.[] | "\(.name) type=\(.type) status=\(.status)"'
Expected output includes:
crowdsec@file type=crowdsec status=enabled
If crowdsec@file is missing, the bouncer is not protecting your routers. Check sudo journalctl -u traefik -n 50.
Step 9 - Confirm CrowdSec is reading Traefik's access log
This is the detection half of the loop. CrowdSec must be attached to /var/lib/traefik/access.log.
sudo cscli metrics show acquisition
You should see a row for file:/var/lib/traefik/access.log with a non-zero Lines parsed count once traffic has flowed. A row that reads lines but parses none would mean the parser is not matching.
sudo cscli collections list | grep -i traefik

Step 10 - Watch the protection work end to end
You can prove the whole loop in under a minute. Add a ban for a test address, then make a request that appears to come from it. 203.0.113.42 is a documentation-only address from RFC 5737, so this is safe to run.
sudo cscli decisions add --ip 203.0.113.42 --duration 2m --reason "guide demonstration"
sudo cscli decisions list
Now request the site as that address. The appliance trusts X-Forwarded-For only from the machine itself, which is why this works locally.
for i in $(seq 1 15); do
CODE=$(curl -s -o /dev/null -w '%{http_code}' -H 'X-Forwarded-For: 203.0.113.42' http://127.0.0.1/)
[ "$CODE" = "403" ] && break
sleep 2
done
echo "banned address: HTTP $CODE"
curl -s -o /dev/null -w 'unrelated address: HTTP %{http_code}\n' -H 'X-Forwarded-For: 203.0.113.99' http://127.0.0.1/
Expected output:
banned address: HTTP 403
unrelated address: HTTP 200
The block is specific to the offending address; everyone else is served normally. A blocked visitor sees this page:

Lift the ban again:
sudo cscli decisions delete --ip 203.0.113.42

In normal operation you do not add decisions by hand. CrowdSec creates them itself when a source trips one of its scenarios, for example scanning for .env files or probing known CVE paths.
Step 11 - Publish your own service behind the proxy
Drop a YAML file into /etc/traefik/dynamic/. Traefik watches the directory and reloads automatically, so no restart is needed. Attach the crowdsec middleware and your service is protected by the same loop.
# /etc/traefik/dynamic/my-app.yml
http:
routers:
my-app:
rule: "Host(`app.example.com`)"
entryPoints:
- web
middlewares:
- crowdsec
service: my-app
services:
my-app:
loadBalancer:
servers:
- url: "http://127.0.0.1:3000"
The built-in landing router has priority 1, the lowest, so any router you add takes precedence over the default page.
Step 12 - Review what the engine is doing
sudo cscli metrics
sudo cscli alerts list
sudo cscli decisions list
sudo cscli scenarios list | head -15

To exempt your own office or management network from ever being banned, edit the whitelist and reload. Loopback and the RFC 1918 private ranges are already exempt.
# /etc/crowdsec/parsers/s02-enrich/cloudimg-admin-whitelist.yaml
whitelist:
reason: "cloudimg trusted management networks"
ip:
- "127.0.0.1"
cidr:
- "10.0.0.0/8"
- "203.0.113.0/24" # <- add your own office CIDR here
Then apply it:
sudo systemctl reload crowdsec
Getting the real client IP behind a load balancer
If you put Azure Load Balancer, Application Gateway or Front Door in front of this VM, Traefik sees the proxy's address rather than the visitor's, and CrowdSec would ban the proxy. Add your front-end ranges to the forwardedHeaders.trustedIPs list for the web and websecure entrypoints in /etc/traefik/traefik.yml, then restart Traefik:
entryPoints:
web:
address: ":80"
forwardedHeaders:
trustedIPs:
- "127.0.0.1/32"
- "10.0.0.0/8" # <- your load balancer subnet
Enabling HTTPS
A Let's Encrypt resolver named letsencrypt is pre-configured and stores certificates on the data disk at /var/lib/traefik/acme.json. Set your contact address in /etc/traefik/traefik.yml, point a DNS name at the VM, then attach the resolver to a router that listens on websecure:
http:
routers:
my-app-secure:
rule: "Host(`app.example.com`)"
entryPoints:
- websecure
middlewares:
- crowdsec
service: my-app
tls:
certResolver: letsencrypt
Optional - community blocklists and the CrowdSec console
The appliance deliberately ships with no CrowdSec Central API enrollment, so nothing is shared and no credential is baked into the image. Everything above works entirely offline. If you would like community blocklists and the hosted console, opt in:
sudo cscli capi register
sudo systemctl restart crowdsec
sudo cscli console enroll YOUR_ENROLLMENT_KEY
Maintenance
Ubuntu security updates are applied automatically by unattended-upgrades. CrowdSec is installed from its official repository and upgrades with the OS. Keep the hub content current with:
sudo cscli hub update
Traefik is a single binary at /usr/local/bin/traefik; to move to a newer release, replace the binary and restart traefik.service. Access and engine logs live on the data disk at /var/lib/traefik and rotate daily for 14 days.
Support
Every cloudimg image is backed by 24/7 support. Contact us at support@cloudimg.co.uk.