<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Crowdsec – Serverküche</title><link>https://serverkueche.de/en/tags/crowdsec/</link><description>Crowdsec – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Fri, 24 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/crowdsec/index.xml" rel="self" type="application/rss+xml"/><item><title>CrowdSec: modern, collaborative intrusion prevention</title><link>https://serverkueche.de/en/tutorials/crowdsec-setup/</link><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/crowdsec-setup/</guid><description>Set up CrowdSec with the Traefik bouncer: detect attacks from the access logs, block attackers with a 403 and benefit from the community blocklist.</description><content:encoded><![CDATA[<p><a href="/en/tutorials/fail2ban-setup/">Fail2ban</a> blocks brute force on your SSH access – but what about the scanners that probe your web apps for holes all day long? CrowdSec closes exactly this gap: it detects attacks in Traefik&rsquo;s access logs and bans the attackers before they even reach your applications.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, a <strong>CrowdSec agent</strong> runs alongside your Traefik, reading along with the <strong>access logs</strong>, detecting suspicious behavior based on <strong>scenarios</strong> (port scans, path probing, known CVE exploitation, login brute force) and putting attacker IPs on a block list. A <strong>bouncer</strong> – here as a Traefik plugin – enforces these decisions: a banned IP only gets <code>403 Forbidden</code> at <strong>every</strong> one of your apps. Tested with <strong>CrowdSec v1.7.8</strong>, the <strong>Traefik bouncer plugin v1.6.0</strong> and <strong>Traefik v3.7</strong>.</p>
<p>This is how the parts interlock:</p>
<ul>
<li><strong>Agent + Local API (LAPI):</strong> reads the logs, evaluates them against the scenarios and manages the active bans (&ldquo;decisions&rdquo;). That&rsquo;s the container we set up.</li>
<li><strong>Bouncer:</strong> asks the LAPI on every request &ldquo;is this IP banned?&rdquo; and blocks if yes. The Traefik bouncer runs as a plugin directly in the reverse proxy – no additional container.</li>
<li><strong>Community blocklist:</strong> CrowdSec reports (anonymized) detected attacks to the central network and gets a <strong>blocklist fed by all users</strong> back in return. IPs that were already flagged as malicious elsewhere are then banned at your place from the start. That&rsquo;s the &ldquo;collaborative&rdquo; in the title.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>CrowdSec instead of fail2ban – or in addition?
  </p>
  <div class="prose-kitchen text-sm">CrowdSec doesn&rsquo;t replace fail2ban, it complements it. <strong>fail2ban</strong> is unbeatably simple for <strong>a single service on the host</strong> (SSH). <strong>CrowdSec</strong> plays to its strength with <strong>HTTP apps behind the reverse proxy</strong>: one bouncer protects all apps at once, the scenarios are maintained and current, and you get the community blocklist for free. The clean split: <strong>fail2ban stays on SSH</strong>, <strong>CrowdSec takes over the web layer</strong>. Both may run in parallel.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A running <a href="/en/tutorials/traefik-reverse-proxy/">reverse proxy with Traefik</a> (the <code>proxy</code> network and the resolver <code>le</code> come from there) with at least one app behind it.</li>
<li><a href="/en/tutorials/fail2ban-setup/">Fail2ban</a> for SSH protection – CrowdSec handles the HTTP layer here, not SSH.</li>
<li>Basic knowledge of <a href="/en/tutorials/docker-compose-basics/">Docker Compose</a> and a hardened server with an active <a href="/en/tutorials/firewall-ufw-setup/">firewall</a>.</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 2000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">8 vCores · 16 GB RAM · 512 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €19.24/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">CrowdSec runs with a few hundred MB alongside your existing app stack – the VPS 2000 has enough reserve for that.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-2000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € voucher</strong> for new netcup customers:</span>
  <code class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-enable-traefiks-access-logs">Step 1: Enable Traefik&rsquo;s access logs</h3>
<p>CrowdSec can only detect what it sees – and it sees attacks via <strong>Traefik&rsquo;s access logs</strong>. They&rsquo;re off by default. Open the <code>compose.yaml</code> of your Traefik (from the <a href="/en/tutorials/traefik-reverse-proxy/">Traefik tutorial</a>, folder <code>~/traefik</code>) and add these three lines to the <code>command</code> block:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--accesslog=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--accesslog.filepath=/var/log/traefik/access.log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--accesslog.format=json&#34;</span></span></span></code></pre></div>
</div>
<p>The JSON format is important – the CrowdSec parsers expect it that way. So that both Traefik (writing) and CrowdSec (reading) can reach the same file, you put it on a fixed host path. Add to Traefik&rsquo;s <code>volumes</code> block:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/log/traefik:/var/log/traefik</span></span></span></code></pre></div>
</div>
<p>Create the directory on the host and restart Traefik:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /var/log/traefik
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/traefik <span class="o">&amp;&amp;</span> docker compose up -d</span></span></code></pre></div>
</div>
<p>Call one of your apps once and check that lines arrive:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tail -n <span class="m">2</span> /var/log/traefik/access.log</span></span></code></pre></div>
</div>
<p>You should see JSON lines with <code>&quot;ClientHost&quot;</code>, <code>&quot;RequestPath&quot;</code> and <code>&quot;DownstreamStatus&quot;</code>. If the file stays empty, the path in the volume is wrong, or simply no request has come in yet.</p>
<h3 id="step-2-set-up-the-crowdsec-container">Step 2: Set up the CrowdSec container</h3>
<p>CrowdSec gets its own folder. First generate a <strong>bouncer key</strong> – the Traefik bouncer later authenticates with it at the LAPI:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/crowdsec <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/crowdsec
</span></span><span class="line"><span class="cl">openssl rand -hex <span class="m">32</span></span></span></code></pre></div>
</div>
<p>Copy the output; it&rsquo;s used in <strong>two</strong> places shortly. Create the <strong>acquisition</strong> – it tells CrowdSec which file with which log type to read:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># ~/crowdsec/acquis.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">filenames</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">/var/log/traefik/access.log</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">traefik</span></span></span></code></pre></div>
</div>
<p>Now the <code>compose.yaml</code>. Replace <code>YOUR_BOUNCER_KEY</code> with the generated key:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">crowdsec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">crowdsecurity/crowdsec:v1.7.8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">crowdsec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">COLLECTIONS</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;crowdsecurity/traefik&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BOUNCER_KEY_TRAEFIK</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;YOUR_BOUNCER_KEY&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">GID</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./acquis.yaml:/etc/crowdsec/acquis.d/traefik.yaml:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/log/traefik:/var/log/traefik:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">crowdsec-db:/var/lib/crowdsec/data/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">crowdsec-config:/etc/crowdsec/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">crowdsec-db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">crowdsec-config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>What matters here:</p>
<ul>
<li><strong><code>COLLECTIONS: crowdsecurity/traefik</code></strong> installs the matching collection of parsers and scenarios for Traefik logs on the first start (including the HTTP base scenarios).</li>
<li><strong><code>BOUNCER_KEY_TRAEFIK</code></strong> registers a bouncer named <code>TRAEFIK</code> with <em>your</em> key right at startup – that saves the detour via <code>cscli bouncers add</code>.</li>
<li><strong><code>GID: &quot;1000&quot;</code></strong> runs the CrowdSec process under this group. <code>1000</code> is the usual default for the first user; if Traefik runs as root, the <code>access.log</code> is usually world-readable and the GID doesn&rsquo;t matter anyway. If it&rsquo;s set more tightly, check with <code>ls -l /var/log/traefik/access.log</code> and enter the GID of the <strong>file&rsquo;s group</strong>.</li>
<li>The log is mounted <strong>read-only</strong> (<code>:ro</code>). CrowdSec is on the <strong><code>proxy</code> network</strong> so the Traefik bouncer can reach the LAPI at <code>crowdsec:8080</code> later.</li>
</ul>
<p>Start the container and check that everything takes effect:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli collections list</span></span></code></pre></div>
</div>
<p>The Traefik collection and the HTTP scenarios should appear as <code>enabled</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> crowdsecurity/base-http-scenarios    ✔️  enabled
</span></span><span class="line"><span class="cl"> crowdsecurity/http-cve               ✔️  enabled
</span></span><span class="line"><span class="cl"> crowdsecurity/traefik                ✔️  enabled</span></span></code></pre></div>
</div>
<p>Check that the <strong>community blocklist</strong> is active – it costs nothing and is half the benefit of CrowdSec:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli capi status</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">You can successfully interact with Central API (CAPI)
</span></span><span class="line"><span class="cl">Sharing signals is enabled
</span></span><span class="line"><span class="cl">Pulling community blocklist is enabled
</span></span><span class="line"><span class="cl">Pulling blocklists from the console is enabled</span></span></code></pre></div>
</div>
<p><code>Pulling community blocklist is enabled</code> confirms that you receive the collective block list of the CrowdSec community – attackers that stood out elsewhere are already banned at your place before they even try.</p>
<h3 id="step-3-add-the-traefik-bouncer">Step 3: Add the Traefik bouncer</h3>
<p>Now the part that enforces the bans. The bouncer is a <strong>Traefik plugin</strong> and consists of two additions to your Traefik: load the plugin and define a middleware that acts on <strong>all</strong> apps.</p>
<p>Load the plugin by adding to the <code>command</code> block of <code>~/traefik/compose.yaml</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--experimental.plugins.bouncer.version=v1.6.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--entrypoints.websecure.http.middlewares=crowdsec@file&#34;</span></span></span></code></pre></div>
</div>
<p>The last line attaches the bouncer middleware <strong>globally to the <code>websecure</code> entrypoint</strong> – so every HTTPS request to every app runs through the bouncer, without you touching each app individually. You define the middleware itself via the <strong>file provider</strong>.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Don&#39;t overwrite existing entrypoint middlewares
  </p>
  <div class="prose-kitchen text-sm"><code>--entrypoints.websecure.http.middlewares</code> is <strong>single-valued</strong>: if you&rsquo;ve already set middlewares on this entrypoint in the <a href="/en/tutorials/traefik-reverse-proxy/">Traefik setup</a> (e.g. security headers), a <strong>second</strong> flag overwrites the existing value – then your previous middlewares silently drop out. So don&rsquo;t set a second flag, but extend the existing value <strong>comma-separated</strong>, e.g. <code>--entrypoints.websecure.http.middlewares=header@file,crowdsec@file</code>. Only if <strong>no</strong> middleware is attached to this entrypoint so far do you add the flag anew as above.</div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>No second file-provider flag
  </p>
  <div class="prose-kitchen text-sm">The <a href="/en/tutorials/traefik-reverse-proxy/">Traefik setup</a> usually already has the file provider active (e.g. as <code>--providers.file.filename=…</code> or <code>--providers.file.directory=…</code>). Traefik does <strong>not</strong> allow <code>filename</code> and <code>directory</code> at the same time – so don&rsquo;t add a second flag blindly. Put the following middleware file either into your <strong>existing</strong> dynamic directory, or – if your provider points to a single <code>filename</code> file – write the <code>http.middlewares</code> block right in there. The paths below (<code>~/traefik/dynamic</code> or <code>/etc/traefik/dynamic</code>) only apply if you already use a directory.</div>
</div>
<p>Create the middleware (path adapted to your existing file provider):</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># ~/traefik/dynamic/crowdsec.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">http</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">middlewares</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">crowdsec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">plugin</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bouncer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">crowdsecMode</span><span class="p">:</span><span class="w"> </span><span class="l">stream</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">crowdsecLapiScheme</span><span class="p">:</span><span class="w"> </span><span class="l">http</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">crowdsecLapiHost</span><span class="p">:</span><span class="w"> </span><span class="l">crowdsec:8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">crowdsecLapiKey</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;YOUR_BOUNCER_KEY&#34;</span></span></span></code></pre></div>
</div>
<p><code>crowdsecLapiKey</code> is the <strong>same</strong> key as in step 2 – the bouncer identifies itself with it at the LAPI. With <code>crowdsecMode: stream</code> the bouncer pulls the block list completely in advance at a fixed interval and answers every request from the local cache – the mode recommended by the plugin, because it doesn&rsquo;t load the LAPI on every request.</p>
<p>Make sure Traefik also <strong>sees</strong> the directory. If your existing file provider already uses a mounted dynamic directory, the new file is in it anyway – then there&rsquo;s nothing to do here. If you create the directory anew, add it to Traefik&rsquo;s <code>volumes</code> block:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./dynamic:/etc/traefik/dynamic:ro</span></span></span></code></pre></div>
</div>
<p>Restart Traefik so it downloads the plugin and loads the middleware:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> ~/traefik <span class="o">&amp;&amp;</span> docker compose up -d
</span></span><span class="line"><span class="cl">docker compose logs -f traefik</span></span></code></pre></div>
</div>
<p>The log must show <strong>no</strong> <code>ERR</code> about the plugin or the middleware (Traefik v3 logs only errors at the default level – a quiet output is the good sign). Check from the CrowdSec side that the bouncer has connected:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli bouncers list</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> Name     IP Address  Valid  Last API pull         Type
</span></span><span class="line"><span class="cl"> TRAEFIK  172.19.0.2  ✔️      2026-07-23T10:49:58Z  Crowdsec-Bouncer-Traefik-Plugin</span></span></code></pre></div>
</div>
<p>A set <code>Last API pull</code> proves: the plugin talks to the LAPI.</p>
<h3 id="step-4-arm-it-and-test-the-ban">Step 4: Arm it and test the ban</h3>
<p>Call one of your apps – it should respond <strong>perfectly normally</strong>:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://YOUR_APP.YOUR_DOMAIN/</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">200</span></span></code></pre></div>
</div>
<p>Now you test-ban an IP by hand and see whether the bouncer locks it out. Take your own public IP (<code>curl -s https://ifconfig.me</code>):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli decisions add --ip YOUR_TEST_IP --duration 5m --reason <span class="s2">&#34;test&#34;</span></span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>New bans don&#39;t take effect immediately
  </p>
  <div class="prose-kitchen text-sm">The bouncer fetches the decisions in the background and <strong>caches</strong> them – it can take <strong>up to ~60 seconds</strong> for a fresh ban to take effect. That&rsquo;s no bug but intended (less load on the LAPI). In <strong>stream</strong> mode the delay comes from the pull interval (<code>updateIntervalSeconds</code>, default 60 seconds), in <strong>live</strong> mode from the per-IP cache of each decision (<code>defaultDecisionSeconds</code>, default 60 seconds). In real operation this is irrelevant; when testing you have to wait briefly. Whoever wants to reduce the reaction time lowers the respective value – i.e. <code>updateIntervalSeconds</code> (stream) or <code>defaultDecisionSeconds</code> (live) – in the middleware, instead of switching modes.</div>
</div>
<p>After the wait, the banned IP only gets:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://YOUR_APP.YOUR_DOMAIN/</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">403</span></span></code></pre></div>
</div>
<p>Lift the test ban again:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli decisions delete --ip YOUR_TEST_IP</span></span></code></pre></div>
</div>
<h3 id="step-5-let-it-detect-real-attacks">Step 5: Let it detect real attacks</h3>
<p>The actual point: CrowdSec bans <strong>on its own</strong>, without you writing rules. As soon as a scanner probes your domain for paths (many <code>404</code> in a short time), the scenario <code>crowdsecurity/http-probing</code> kicks in. This is what a real, automatically detected ban looks like:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli alerts list</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> ID  value              reason                       country  as                      decisions
</span></span><span class="line"><span class="cl"> 4   Ip:109.250.21.197  crowdsecurity/http-probing   DE       8881 1&amp;1 Versatel GmbH  ban:1</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli decisions list</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> Source    Scope:Value        Reason                       Action  Country  AS
</span></span><span class="line"><span class="cl"> crowdsec  Ip:109.250.21.197  crowdsecurity/http-probing   ban     DE       8881 1&amp;1 Versatel GmbH</span></span></code></pre></div>
</div>
<p>CrowdSec automatically enriches the ban with <strong>country and provider (AS)</strong>. Besides the self-detected attacks, bans with the source <code>lists:crowdsecurity/...</code> appear here over time – that&rsquo;s the <strong>community blocklist</strong>: IPs that already stood out negatively worldwide, you block before they hit you the first time.</p>
<p>Whoever prefers to see the bans graphically instead of in <code>cscli</code> tables enrolls the instance for free in the <a href="https://app.crowdsec.net/">CrowdSec console</a> (the enroll command is in &ldquo;Maintenance &amp; backups&rdquo;). There the same decisions appear presented nicely:</p>
<p><figure class="my-6"><img src="/en/tutorials/crowdsec-setup/crowdsec-console-decisions_hu_edda9bf11738a503.webp" srcset="/en/tutorials/crowdsec-setup/crowdsec-console-decisions_hu_c4a4dc9c1c0bad75.webp 480w, /en/tutorials/crowdsec-setup/crowdsec-console-decisions_hu_edda9bf11738a503.webp 768w, /en/tutorials/crowdsec-setup/crowdsec-console-decisions_hu_c963e2d6d8f9a3d2.webp 1200w, /en/tutorials/crowdsec-setup/crowdsec-console-decisions_hu_7abd8b2f243af422.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="360"
    data-full="/en/tutorials/crowdsec-setup/crowdsec-console-decisions_hu_c6e0877ac3db367b.webp"
    alt="The CrowdSec console lists the active bans as a table: several IP addresses with the action &ldquo;ban&rdquo;, an expiry time and the triggering scenario http-probing, each &ldquo;via crowdsec&rdquo;" title="The CrowdSec console (optional, free): the same decisions as in cscli, just graphical"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The CrowdSec console (optional, free): the same decisions as in cscli, just graphical</figcaption></figure></p>
<p>The HTTP scenarios installed with the Traefik collection cover what real scanners try all day: <strong>probing non-existent paths</strong> (<code>http-probing</code>), searching for <strong>sensitive files and backdoors</strong>, <strong>path traversal</strong> and <strong>SQL injection/XSS</strong> probes, suspicious <strong>user agents</strong> of known scanners, as well as the exploitation of <strong>known CVEs</strong> (from the collection <code>crowdsecurity/http-cve</code>). Which scenarios are active is shown by <code>docker exec crowdsec cscli scenarios list</code>.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>One notch sharper: AppSec (WAF)
  </p>
  <div class="prose-kitchen text-sm">Beyond pure IP blocking, CrowdSec can work as a <strong>web application firewall</strong> and detect malicious requests directly by their content (virtual patching). The Traefik plugin supports this AppSec mode (from plugin 1.2.0 and CrowdSec 1.6.0). For getting started, the scenario-based blocking shown here is enough; AppSec is the worthwhile next expansion step when a particularly exposed app needs additional protection.</div>
</div>
<p>Whether CrowdSec reads your logs at all is shown by the metrics overview:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli metrics</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">| Source                           | Lines read | Lines parsed |
</span></span><span class="line"><span class="cl">| file:/var/log/traefik/access.log | 54         | 54           |</span></span></code></pre></div>
</div>
<p>If it says <code>Lines read: 0</code>, no log is arriving – then back to step 1.</p>
<h3 id="step-6-dont-lock-yourself-out">Step 6: Don&rsquo;t lock yourself out</h3>
<p>A false alarm against your own maintenance IP is annoying. Enter your fixed IP as <strong>trusted</strong> in the middleware – the bouncer then doesn&rsquo;t even check it. Add to <code>~/traefik/dynamic/crowdsec.yaml</code> under <code>bouncer:</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">          </span><span class="nt">clientTrustedIPs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="s2">&#34;YOUR_FIXED_IP/32&#34;</span></span></span></code></pre></div>
</div>
<p>If you did lock yourself out, one command lifts the ban immediately – and in an emergency you can always reach the server via the <a href="/en/tutorials/netcup-snapshots-scp/">VNC console in the SCP</a>:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli decisions delete --ip YOUR_IP   <span class="c1"># one IP</span>
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> crowdsec cscli decisions delete --all           <span class="c1"># all bans</span></span></span></code></pre></div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>A freshly set ban doesn&rsquo;t take effect, the IP keeps getting <code>200</code>.</strong> The bouncer caches the
decisions and, in stream mode, pulls new ones only after the pull interval (up to ~60 seconds). Wait
briefly. Whoever wants to reduce the reaction time lowers <code>updateIntervalSeconds</code> in the middleware
(step 3).</p>
<p><strong><code>cscli metrics</code> shows <code>Lines read: 0</code> for the log file.</strong> CrowdSec isn&rsquo;t reading the logs. Check
that Traefik really writes to <code>/var/log/traefik/access.log</code> (step 1), that <strong>both</strong> containers mount
the same host path, and that the <code>acquis.yaml</code> names exactly this path.</p>
<p><strong>After restarting Traefik, all apps get an error or don&rsquo;t load.</strong> In <strong>stream</strong> mode (set above),
the bouncer can lock down after several failed LAPI pulls – controlled via <code>updateMaxFailure</code>. If
the CrowdSec LAPI is unreachable, the bouncer then blocks in doubt instead of letting through
unprotected. That&rsquo;s safe, but makes CrowdSec a <strong>critical component</strong>: if the <code>crowdsec</code> container
isn&rsquo;t running, your site is affected. Check with <code>docker ps</code> that it&rsquo;s <code>Up</code>; <code>restart: unless-stopped</code> (set above) brings it back automatically after a restart.</p>
<p><strong>CrowdSec bans lots of harmless or no real visitors – the detected IP is always your CDN&rsquo;s.</strong> If a
CDN like Cloudflare sits <strong>in front of</strong> Traefik, Traefik only sees its IP. Then have the real
client IP read from the <code>X-Forwarded-For</code> header: fill <code>forwardedHeadersTrustedIPs</code> in the
middleware with the CDN networks – otherwise you end up banning the CDN. Directly at netcup (without
a CDN) there&rsquo;s nothing to do here.</p>
<p><strong><code>docker compose up -d</code> on Traefik reports a plugin error.</strong> Traefik downloads the plugin from the
net on startup. Check that the server may go out (the <a href="/en/tutorials/netcup-firewall-setup/">netcup
firewall</a> blocks nothing <strong>outbound</strong>, UFW likewise) and that
<code>modulename</code> and <code>version</code> are exactly right.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li>
<p><strong>Keep scenarios current.</strong> CrowdSec&rsquo;s strength is maintained detection rules – pull them regularly: <code>docker exec crowdsec cscli hub update &amp;&amp; docker exec crowdsec cscli hub upgrade</code>. You bump the image (<code>crowdsecurity/crowdsec</code>) and the <strong>plugin version</strong> (<code>v1.6.0</code>) deliberately; read the release notes on major versions. CrowdSec develops briskly – a good candidate for regular version checks.</p>
</li>
<li>
<p><strong>Rotate the access log.</strong> Traefik&rsquo;s <code>/var/log/traefik/access.log</code> otherwise grows unbounded. Set up a logrotate rule – mandatorily with <strong><code>copytruncate</code></strong>: this variant copies the file away and empties the original <em>in place</em>, instead of renaming it and creating a new one. Only this way does CrowdSec&rsquo;s tail keep the file access (with rename/create it would lose the handle and read nothing anymore). Create <code>/etc/logrotate.d/traefik</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/var/log/traefik/access.log {
</span></span><span class="line"><span class="cl">    daily
</span></span><span class="line"><span class="cl">    rotate 7
</span></span><span class="line"><span class="cl">    compress
</span></span><span class="line"><span class="cl">    missingok
</span></span><span class="line"><span class="cl">    notifempty
</span></span><span class="line"><span class="cl">    copytruncate
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div>
</div>
<p>After the truncate, CrowdSec reads on perfectly normally from the file that&rsquo;s growing again – a restart of the container isn&rsquo;t needed.</p>
</li>
<li>
<p><strong>fail2ban stays.</strong> Don&rsquo;t remove fail2ban – it continues to protect your SSH access at the host level, while CrowdSec covers the web apps. Two tools, two responsibilities.</p>
</li>
<li>
<p><strong>Backups.</strong> What you must back up is above all your <strong>configuration</strong>: the folder <code>~/crowdsec</code> (<code>compose.yaml</code>, <code>acquis.yaml</code>) and <code>~/traefik/dynamic/crowdsec.yaml</code> with the bouncer key. Include them in your <a href="/en/tutorials/restic-backups/">Restic backup</a>. The block lists themselves are ephemeral and rebuild on their own – no backup needed.</p>
</li>
<li>
<p><strong>Optional: the CrowdSec dashboard.</strong> Whoever wants to see the attacks graphically enrolls the instance for free in the <a href="https://app.crowdsec.net/">CrowdSec console</a> (<code>docker exec crowdsec cscli console enroll YOUR_ENROLL_KEY</code>). The community blocklist runs without enrollment too.</p>
</li>
<li>
<p><strong>Integrate it into your monitoring.</strong> <code>cscli metrics</code> gives you numbers for the terminal; for continuous observation, CrowdSec provides a Prometheus endpoint (enable it in the config under <code>prometheus:</code>) that you can hang on your <a href="/en/tutorials/monitoring-grafana-prometheus/">Grafana stack</a>. Then you see bans by origin and scenario at a glance:</p>
<p><figure class="my-6"><img src="/en/tutorials/crowdsec-setup/crowdsec-grafana-dashboard_hu_8bf8cd1517fc9096.webp" srcset="/en/tutorials/crowdsec-setup/crowdsec-grafana-dashboard_hu_9232ed57309a2207.webp 480w, /en/tutorials/crowdsec-setup/crowdsec-grafana-dashboard_hu_8bf8cd1517fc9096.webp 768w, /en/tutorials/crowdsec-setup/crowdsec-grafana-dashboard_hu_a4133624712c35ed.webp 1200w, /en/tutorials/crowdsec-setup/crowdsec-grafana-dashboard_hu_c65d2820e7b1facb.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/crowdsec-setup/crowdsec-grafana-dashboard_hu_2209b8ac321bafec.webp"
    alt="A Grafana dashboard with CrowdSec metrics: around 15,000 active bans total, broken down by origin (community blocklist vs. locally detected) and by scenario like ssh:bruteforce, generic:scan and http-probing" title="CrowdSec in Grafana: active bans by origin and scenario, fed from the Prometheus endpoint"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">CrowdSec in Grafana: active bans by origin and scenario, fed from the Prometheus endpoint</figcaption></figure></p>
</li>
<li>
<p><strong>Honest about the effort:</strong> After setup, CrowdSec runs largely on its own. Plan a <code>hub upgrade</code> and a look at <code>cscli metrics</code> and the container version once a month.</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>