Security Azure

Traefik with CrowdSec Security Engine on Ubuntu 24.04 on Azure User Guide

| Product: Traefik with CrowdSec Security Engine on Ubuntu 24.04 LTS on Azure

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/traefik collection, which parses Traefik's access log, plus the linux and sshd collections
  • web (:80), websecure (:443) and a loopback ping (127.0.0.1:8082) entrypoint
  • The Traefik dashboard at /dashboard/ behind the bouncer and HTTP basic auth (api.insecure is false)
  • 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/traefik for 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

Traefik and CrowdSec service status and versions on the cloudimg appliance

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.

The default landing page, served through the CrowdSec bouncer middleware

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.

Traefik dashboard showing entrypoints, routers and services all healthy

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 crowdsec middleware registered and enabled in the Traefik dashboard

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

CrowdSec acquisition metrics showing the Traefik access log being parsed

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:

The block page shown to a visitor refused by the CrowdSec Security Engine

Lift the ban again:

sudo cscli decisions delete --ip 203.0.113.42

A live ban decision and the resulting 403 at the edge

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

CrowdSec engine health and the loaded scenarios

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.