<?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>Rate-Limit – Serverküche</title><link>https://serverkueche.de/tags/rate-limit/</link><description>Rate-Limit – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>de-DE</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Thu, 01 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/rate-limit/index.xml" rel="self" type="application/rss+xml"/><item><title>Traefik härten: Security-Header, Rate-Limit, BasicAuth &amp; IP-Allowlist</title><link>https://serverkueche.de/tutorials/traefik-middlewares-absichern/</link><pubDate>Thu, 01 Oct 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/traefik-middlewares-absichern/</guid><description>Traefik-Middlewares in v3 richtig einsetzen: Security-Header setzen, Anfragen begrenzen, Dienste mit BasicAuth und IP-Allowlist schützen – mit curl geprüft.</description><content:encoded><![CDATA[<p>Dein Traefik verteilt Anfragen und holt Zertifikate – aber er schützt noch nichts. Vier
Middlewares ändern das: Security-Header, ein Rate-Limit, BasicAuth und eine IP-Allowlist. Wir bauen
sie einzeln auf und <strong>messen nach jedem Schritt mit <code>curl</code></strong>, was sich wirklich geändert hat.</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Am Ende hast du vier wiederverwendbare Middlewares und eine Kette, die du an jeden Dienst hängen
kannst:</p>
<table>
	<thead>
			<tr>
					<th>Middleware</th>
					<th>Wirkung</th>
					<th>Nachgemessen</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>sec-headers</code></td>
					<td>HSTS, Clickjacking- und MIME-Schutz</td>
					<td>5 neue Antwort-Header</td>
			</tr>
			<tr>
					<td><code>rate</code></td>
					<td>begrenzt Anfragen pro IP</td>
					<td>40 parallele Anfragen → 13× <code>200</code>, 27× <code>429</code></td>
			</tr>
			<tr>
					<td><code>auth</code></td>
					<td>BasicAuth vor dem Dienst</td>
					<td>ohne Passwort <code>401</code>, mit <code>200</code></td>
			</tr>
			<tr>
					<td><code>nur-ich</code></td>
					<td>nur deine IP darf rein</td>
					<td>fremde IP <code>403</code>, eigene <code>200</code></td>
			</tr>
	</tbody>
</table>
<p>Getestet mit <strong>Traefik v3.7.8</strong> auf Debian 13. Wichtig für Umsteiger: In Traefik v3 heißt die
Allowlist <code>ipAllowList</code> – den v2-Namen <code>ipWhiteList</code> gibt es nicht mehr, entsprechende
Konfigurationen brechen beim Upgrade.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Ein <strong>laufender Traefik v3</strong> mit <code>proxy</code>-Netz und funktionierendem TLS, wie im
<a href="/tutorials/reverse-proxy-traefik/">Reverse Proxy mit Traefik</a> aufgebaut</li>
<li>Ein Dienst dahinter, an dem du üben kannst. Wir nehmen <code>traefik/whoami</code> – ein winziger Container,
der jede Anfrage samt Headern zurückgibt, also perfekt zum Prüfen</li>
<li>Grundverständnis von <a href="/tutorials/docker-compose-grundlagen/">Docker Compose</a> und Labels</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"
     data-track-content data-content-name="Affiliate-Box · /tutorials/traefik-middlewares-absichern/" data-content-piece="VPS 1000 G12.5">
  <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>🍳 Empfehlung</span>
    <span title="Mit * markierte Links sind Affiliate-Links.">Anzeige</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 1000 G12.5</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCore · 8 GB RAM · 128 GB SSD</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 14,50 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Middlewares kosten praktisch keine Ressourcen – das läuft auf dem kleinsten VPS.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-1000-g12.5-24m-eu?ref=44083#vps-1000-g12.5-12m-eu" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12.5 · {page}"
   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">
  Zu netcup →
</a>

  </div><div class="px-4 pb-4"><div class="not-prose my-3 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"
     data-voucher-pool="vps-1000" data-voucher-codes="[&#34;6877nc17905136544&#34;,&#34;6877nc17905136543&#34;,&#34;6877nc17905136542&#34;,&#34;6877nc17905136541&#34;,&#34;6877nc17905136540&#34;,&#34;6877nc17905133999&#34;,&#34;6877nc17905133998&#34;,&#34;6877nc17905133997&#34;,&#34;6877nc17905133996&#34;,&#34;6877nc17905133995&#34;,&#34;6877nc17905133994&#34;,&#34;6877nc17905133993&#34;,&#34;6877nc17905133992&#34;,&#34;6877nc17905133991&#34;,&#34;6877nc17905133990&#34;]">
  <p class="flex flex-wrap items-center gap-x-2 gap-y-1">
    <span>💶 <span class="font-semibold">1 Monat gratis</span> für netcup-Neukunden:</span>
    <button type="button" data-voucher-code data-track-voucher="6877nc17905136544"
            title="Zum Kopieren klicken" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">6877nc17905136544</button>
    <button type="button" data-voucher-next hidden
            class="rounded border border-herb-500/40 px-2 py-0.5 text-xs text-herb-800 hover:bg-white dark:text-herb-400 dark:hover:bg-slate-800">anderer Code</button>
  </p>
  <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
    Einmal einlösbar, gilt für VPS 1000 G12.5.
    <a href="https://www.netcup.com/de/checkout/warenkorb?ref=44083" rel="sponsored noopener" target="_blank"
       data-track-event="Affiliate|netcup: Gutschein einlösen|VPS 1000 G12.5 · {page}"
       class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Im Warenkorb einlösen →</a>
  </p><p class="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 border-t border-herb-500/30 pt-2">
    <span>💶 <span class="font-semibold">5 € Gutschein</span> für netcup-Neukunden:</span>
    <button type="button" data-voucher-code data-track-voucher="36nc17844976032"
            title="Zum Kopieren klicken" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</button>
    <span class="text-xs text-slate-500 dark:text-slate-400">immer gültig · nicht für Domains und VPS Lite</span>
  </p>
</div></div>
</div>

<h2 id="schritt-für-schritt">Schritt für Schritt</h2>
<h3 id="1-ausgangslage-messen">1. Ausgangslage messen</h3>
<p>Bevor du etwas härtest, halte fest, wie es vorher war. Leg einen Testdienst an –
<code>~/whoami/compose.yaml</code>, <code>DEINE_DOMAIN</code> ersetzen:</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">whoami</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">traefik/whoami:v1.12</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 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">labels</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;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.whoami.rule=Host(`whoami.DEINE_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.whoami.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.whoami.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.whoami.loadbalancer.server.port=80&#34;</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>
<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 ~/whoami <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/whoami <span class="o">&amp;&amp;</span> docker compose up -d</span></span></code></pre></div>
</div>
<p>Jetzt die Antwort-Header ansehen:</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 -sI https://whoami.DEINE_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">HTTP/2 200
</span></span><span class="line"><span class="cl">content-type: text/plain; charset=utf-8
</span></span><span class="line"><span class="cl">date: Sat, 25 Jul 2026 07:01:27 GMT
</span></span><span class="line"><span class="cl">content-length: 351</span></span></code></pre></div>
</div>
<p>Das ist die ganze Wahrheit: <strong>Traefik setzt von sich aus keinen einzigen Security-Header.</strong> TLS
allein sorgt für einen verschlüsselten Transport – gegen Clickjacking, MIME-Sniffing oder
versehentliches HTTP hilft es nicht.</p>
<h3 id="2-security-header-setzen">2. Security-Header setzen</h3>
<p>Middlewares werden bei Docker-Labels an einem beliebigen Container <strong>definiert</strong> und an einem
Router <strong>aktiviert</strong>. Ergänze die Labels des Testdienstes:</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="c"># aktivieren</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.whoami.middlewares=sec-headers&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># definieren</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.sec-headers.headers.stsSeconds=31536000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.sec-headers.headers.stsIncludeSubdomains=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.sec-headers.headers.stsPreload=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.sec-headers.headers.frameDeny=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.sec-headers.headers.contentTypeNosniff=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.sec-headers.headers.referrerPolicy=strict-origin-when-cross-origin&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.sec-headers.headers.permissionsPolicy=camera=(), microphone=(), geolocation=()&#34;</span></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 compose up -d <span class="o">&amp;&amp;</span> sleep <span class="m">5</span> <span class="o">&amp;&amp;</span> curl -sI https://whoami.DEINE_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">HTTP/2 200
</span></span><span class="line"><span class="cl">content-type: text/plain; charset=utf-8
</span></span><span class="line"><span class="cl">permissions-policy: camera=(), microphone=(), geolocation=()
</span></span><span class="line"><span class="cl">referrer-policy: strict-origin-when-cross-origin
</span></span><span class="line"><span class="cl">strict-transport-security: max-age=31536000; includeSubDomains; preload
</span></span><span class="line"><span class="cl">x-content-type-options: nosniff
</span></span><span class="line"><span class="cl">x-frame-options: DENY</span></span></code></pre></div>
</div>
<p>Fünf Header mehr, und jeder hat einen Zweck:</p>
<ul>
<li><strong><code>strict-transport-security</code></strong> (HSTS) sagt dem Browser: Sprich mit dieser Domain <strong>ein Jahr
lang</strong> nur noch über HTTPS. Das schließt die Lücke zwischen dem Tippen von <code>example.de</code> und dem
Redirect auf HTTPS.</li>
<li><strong><code>x-frame-options: DENY</code></strong> verbietet das Einbetten in fremde <code>&lt;iframe&gt;</code>s – der klassische
Clickjacking-Schutz.</li>
<li><strong><code>x-content-type-options: nosniff</code></strong> verhindert, dass der Browser den Inhaltstyp „errät&quot; und
etwa eine hochgeladene Textdatei als Skript ausführt.</li>
<li><strong><code>referrer-policy</code></strong> verhindert, dass beim Klick auf externe Links deine vollständige interne
URL mitgeschickt wird.</li>
<li><strong><code>permissions-policy</code></strong> schaltet Kamera, Mikrofon und Standort für diese Seite ab.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>preload ist eine Einbahnstraße
  </p>
  <div class="prose-kitchen text-sm"><code>stsPreload=true</code> ist die Einladung, deine Domain in die <strong>HSTS-Preload-Liste</strong> der Browser
aufzunehmen (die Aufnahme selbst beantragst du separat). Ist sie dort, erzwingen Browser HTTPS für
die Domain <strong>und alle Subdomains</strong> – auch wenn dein Server längst kein Zertifikat mehr liefert.
Herauskommen dauert Monate. Setze <code>stsIncludeSubdomains</code> und <code>stsPreload</code> nur, wenn du sicher bist,
dass <strong>jede</strong> Subdomain dauerhaft HTTPS spricht. Für den Anfang reicht <code>stsSeconds</code> allein.</div>
</div>
<p>Bewusst <strong>nicht</strong> in der Liste: <code>browserXssFilter</code>. Der Header <code>X-XSS-Protection</code> gilt als
veraltet, moderne Browser ignorieren ihn. Auch eine <code>contentSecurityPolicy</code> fehlt hier – die ist
wirkungsvoll, aber pro Anwendung verschieden und bricht Oberflächen zuverlässig, wenn man sie
pauschal setzt.</p>
<h3 id="3-rate-limit-gegen-brute-force-und-bots">3. Rate-Limit gegen Brute-Force und Bots</h3>
<p>Diese Middleware begrenzt, wie viele Anfragen <strong>eine IP</strong> stellen darf:</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;traefik.http.middlewares.rate.ratelimit.average=5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.rate.ratelimit.burst=10&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.rate.ratelimit.period=1s&#34;</span></span></span></code></pre></div>
</div>
<p><code>average=5</code> bei <code>period=1s</code> heißt: fünf Anfragen pro Sekunde im Schnitt. <code>burst=10</code> erlaubt kurze
Spitzen darüber – Webseiten laden nun mal mehrere Dateien gleichzeitig.</p>
<p>Aktivieren und mit echter Last prüfen. Wichtig: <strong>parallel</strong> testen, sonst bist du zu langsam, um
das Limit überhaupt zu treffen:</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">seq <span class="m">40</span> <span class="p">|</span> xargs -P <span class="m">8</span> -I<span class="o">{}</span> curl -s -o /dev/null -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://whoami.DEINE_DOMAIN/ <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="p">|</span> sort <span class="p">|</span> uniq -c</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">     13 200
</span></span><span class="line"><span class="cl">     27 429</span></span></code></pre></div>
</div>
<p>Genau so soll es aussehen: Die ersten Anfragen gehen durch, danach antwortet Traefik mit
<code>429 Too Many Requests</code>, ohne den Dienst dahinter überhaupt zu belasten.</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>Sinnvolle Werte
  </p>
  <div class="prose-kitchen text-sm">Für eine normale Weboberfläche sind 5–20 Anfragen pro Sekunde mit Burst 20–50 realistisch. Zu knapp
gesetzt, blockierst du echte Nutzer beim Laden einer Seite. Beobachte nach dem Aktivieren die
Traefik-Logs auf <code>429</code> – tauchen sie im Alltag auf, ist das Limit zu streng.</div>
</div>
<h3 id="4-basicauth-für-dienste-ohne-eigene-anmeldung">4. BasicAuth für Dienste ohne eigene Anmeldung</h3>
<p>Manche Werkzeuge haben keine oder eine schwache Anmeldung – ein Traefik-Dashboard, ein
Metrik-Endpunkt, eine Admin-Oberfläche. BasicAuth setzt eine Hürde davor, bevor die Anfrage die
Anwendung erreicht.</p>
<p>Zuerst einen Passwort-Hash erzeugen. Das geht ohne Extra-Paket, direkt mit Docker:</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 run --rm httpd:2.4-alpine htpasswd -nbB koch <span class="s2">&#34;DEIN_PASSWORT&#34;</span></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">koch:$2y$05$IcOtS/jq6VR4haHO2Pvtl...</span></span></code></pre></div>
</div>
<p><code>-B</code> erzwingt bcrypt (nicht das alte MD5), <code>-nb</code> schreibt das Ergebnis auf die Standardausgabe.</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>Jedes $ muss im Compose verdoppelt werden
  </p>
  <div class="prose-kitchen text-sm">bcrypt-Hashes enthalten <code>$</code>-Zeichen, und Docker Compose interpretiert <code>$</code> als Variable. Trage den
Hash deshalb mit <strong>doppelten</strong> Dollarzeichen ein: aus <code>$2y$05$Ic…</code> wird <code>$$2y$$05$$Ic…</code>. Vergisst du
das, verschwinden Teile des Hashes stillschweigend und die Anmeldung schlägt immer fehl –
<code>docker compose config</code> zeigt dir, was Compose wirklich daraus macht.</div>
</div>
<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;traefik.http.middlewares.auth.basicauth.users=koch:$$2y$$05$$IcOtS/jq6VR4haHO2Pvtl...&#34;</span></span></span></code></pre></div>
</div>
<p>Prüfen – drei Fälle, drei Antworten:</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://whoami.DEINE_DOMAIN/
</span></span><span class="line"><span class="cl">curl -s -o /dev/null -u koch:falsch -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://whoami.DEINE_DOMAIN/
</span></span><span class="line"><span class="cl">curl -s -o /dev/null -u koch:DEIN_PASSWORT -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://whoami.DEINE_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">401
</span></span><span class="line"><span class="cl">401
</span></span><span class="line"><span class="cl">200</span></span></code></pre></div>
</div>
<p>Der Browser fragt beim <code>401</code> selbst nach Zugangsdaten – Traefik schickt dazu
<code>www-authenticate: Basic realm=&quot;traefik&quot;</code>.</p>
<p>BasicAuth ist bewusst simpel: kein Logout, kein zweiter Faktor, keine Nutzerverwaltung. Für
„niemand außer mir soll hier überhaupt klopfen können&quot; ist es genau richtig; für echte
Benutzerkonten über mehrere Dienste ist Single Sign-On der passende Weg.</p>
<h3 id="5-ip-allowlist-nur-von-zu-hause">5. IP-Allowlist: nur von zu Hause</h3>
<p>Noch strenger: Anfragen nur aus bestimmten Netzen zulassen. In Traefik v3 heißt die Middleware
<code>ipAllowList</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;traefik.http.middlewares.nur-ich.ipallowlist.sourcerange=DEINE_IP/32&#34;</span></span></span></code></pre></div>
</div>
<p>Deine öffentliche IP findest du mit <code>curl -s https://ifconfig.me</code>. Mehrere Bereiche trennst du mit
Komma, z. B. <code>sourcerange=203.0.113.5/32,198.51.100.0/24</code>.</p>
<p>Der Test zeigt beide Seiten. Mit einer fremden IP in der Liste antwortet Traefik:</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">403</span></span></code></pre></div>
</div>
<p>Und mit der eigenen:</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">200</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>Welche IP sieht Traefik überhaupt?
  </p>
  <div class="prose-kitchen text-sm">Traefik prüft die IP der <strong>direkten</strong> Verbindung. Steht noch etwas davor – Cloudflare, ein
Load-Balancer, ein weiterer Proxy – siehst du dessen IP, und eine Allowlist mit deiner eigenen
sperrt dich selbst aus. Was tatsächlich ankommt, verrät <code>traefik/whoami</code> im Feld <code>X-Forwarded-For</code>.
Für solche Aufbauten gibt es <code>ipallowlist.ipstrategy.depth</code> bzw. <code>excludedips</code> – prüfe aber vorher,
wie viele Proxys wirklich davor hängen.</div>
</div>
<p>Eine IP-Allowlist ist wirksam, aber unbequem: Am Handy im Mobilfunknetz oder im Hotel-WLAN kommst
du nicht mehr rein. Die elegantere Variante für „nur ich&quot; ist ein
<a href="/tutorials/wireguard-vpn-einrichten/">WireGuard-VPN</a> – dann erlaubst du das VPN-Netz statt
wechselnder öffentlicher IPs.</p>
<h3 id="6-middlewares-zu-einer-kette-bündeln">6. Middlewares zu einer Kette bündeln</h3>
<p>Mehrere Middlewares an einem Router listest du kommagetrennt – <strong>die Reihenfolge ist die
Abarbeitungsreihenfolge</strong>. Damit du das nicht bei jedem Dienst wiederholst, gibt es <code>chain</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;traefik.http.middlewares.gehaertet.chain.middlewares=sec-headers,rate,auth&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.whoami.middlewares=gehaertet&#34;</span></span></span></code></pre></div>
</div>
<p>Prüfen, dass alles gleichzeitig wirkt:</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://whoami.DEINE_DOMAIN/
</span></span><span class="line"><span class="cl">curl -sI -u koch:DEIN_PASSWORT https://whoami.DEINE_DOMAIN/ <span class="p">|</span> grep -i strict-transport</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">401
</span></span><span class="line"><span class="cl">strict-transport-security: max-age=31536000; includeSubDomains; preload</span></span></code></pre></div>
</div>
<p>Ohne Zugangsdaten <code>401</code>, mit Zugangsdaten kommen Antwort <strong>und</strong> Header. Für die Reihenfolge gilt
als Faustregel: <strong>billige Ablehnungen zuerst.</strong> Eine IP-Allowlist verwirft die Anfrage, ohne etwas
zu prüfen; das Rate-Limit kostet fast nichts; BasicAuth muss einen bcrypt-Hash rechnen – und der
ist absichtlich langsam. Also <code>nur-ich</code>, dann <code>rate</code>, dann <code>auth</code>.</p>
<h3 id="7-der-wichtigste-anwendungsfall-das-traefik-dashboard">7. Der wichtigste Anwendungsfall: das Traefik-Dashboard</h3>
<p>Traefik bringt ein Dashboard mit, das alle Router, Services und Middlewares zeigt – im Alltag
ausgesprochen nützlich beim Debuggen. Es ist im Standard <strong>aus</strong>, und das aus gutem Grund: Es
verrät die komplette Struktur deines Servers. Genau hier zahlt sich BasicAuth aus.</p>
<p>Schalte im Traefik-Stack die API ein:</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">command</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;--api.dashboard=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># … die bestehenden Parameter …</span></span></span></code></pre></div>
</div>
<p>Und gib Traefik Labels für sich selbst. Der Trick ist der interne Service <code>api@internal</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">labels</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;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.dashboard.rule=Host(`traefik.DEINE_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.dashboard.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.dashboard.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.dashboard.service=api@internal&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.dashboard.middlewares=dashboard-auth&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$05$$IcOtS/jq6VR4haHO2Pvtl...&#34;</span></span></span></code></pre></div>
</div>
<p>Nach <code>docker compose up -d</code> prüfst du <strong>beide</strong> Einstiegspunkte – das Dashboard und die API, die
darunter liegt:</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://traefik.DEINE_DOMAIN/dashboard/
</span></span><span class="line"><span class="cl">curl -s -o /dev/null -u admin:DEIN_PASSWORT -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://traefik.DEINE_DOMAIN/dashboard/
</span></span><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://traefik.DEINE_DOMAIN/api/overview
</span></span><span class="line"><span class="cl">curl -s -u admin:DEIN_PASSWORT https://traefik.DEINE_DOMAIN/api/overview</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">401
</span></span><span class="line"><span class="cl">200
</span></span><span class="line"><span class="cl">401
</span></span><span class="line"><span class="cl">{&#34;http&#34;:{&#34;routers&#34;:{&#34;total&#34;:3,&#34;warnings&#34;:0,&#34;errors&#34;:0},&#34;services&#34;:{&#34;total&#34;:5, …</span></span></code></pre></div>
</div>
<p>Wichtig ist die dritte Zeile: Die <strong>API ist genauso geschützt wie die Oberfläche</strong>. Hättest du die
Middleware nur an einen <code>/dashboard</code>-Pfad gehängt, wäre <code>/api/…</code> weiterhin offen – und damit die
gesamte Konfiguration lesbar. Weil der Router hier auf den ganzen Host greift, gilt die
Anmeldung für beides.</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>Tipp
  </p>
  <div class="prose-kitchen text-sm">Noch besser als BasicAuth ist beim Dashboard die Kombination: <code>nur-ich</code> (IP-Allowlist) <strong>und</strong>
<code>dashboard-auth</code> als Kette. Dann muss ein Angreifer erst im richtigen Netz sein, bevor er überhaupt ein
Passwort raten darf. Für ein Dashboard, das du selten brauchst, ist das die passende Härte.</div>
</div>
<h3 id="8-middlewares-zentral-definieren-der-skalierbare-weg">8. Middlewares zentral definieren (der skalierbare Weg)</h3>
<p>Bis hier haben wir alles über Labels definiert. Bei fünf Diensten kopierst du dieselben Zeilen
fünfmal – und beim Ändern der HSTS-Dauer editierst du fünf Dateien. Besser: <strong>einmal zentral im
File-Provider</strong> definieren.</p>
<p>Ergänze im Traefik-Stack zwei Startparameter und einen Mount:</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">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># … die bestehenden Parameter …</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--providers.file.directory=/dynamic&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--providers.file.watch=true&#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="c"># … bestehende Mounts …</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./dynamic:/dynamic:ro</span></span></span></code></pre></div>
</div>
<p>Dann die Middlewares als YAML in <code>~/traefik/dynamic/middlewares.yml</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="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">sec-headers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">headers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">stsSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">31536000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">stsIncludeSubdomains</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">stsPreload</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">frameDeny</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">contentTypeNosniff</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">referrerPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">strict-origin-when-cross-origin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">rate</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">rateLimit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">average</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">burst</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">period</span><span class="p">:</span><span class="w"> </span><span class="l">1s</span></span></span></code></pre></div>
</div>
<p>Traefik neu starten – danach hängst du sie an jeden Dienst mit dem Zusatz <strong><code>@file</code></strong>:</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;traefik.http.routers.whoami.middlewares=sec-headers@file,rate@file&#34;</span></span></span></code></pre></div>
</div>
<p>Nachgeprüft: Header und Rate-Limit wirken genauso wie über Labels (im Test 12× <code>200</code>, 18× <code>429</code> bei
30 parallelen Anfragen). Der Gewinn: <code>--providers.file.watch=true</code> liest Änderungen an der Datei
<strong>ohne Neustart</strong> ein, und die HSTS-Dauer steht künftig an genau einer Stelle.</p>
<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>Woher kommt eine Middleware?
  </p>
  <div class="prose-kitchen text-sm">Traefik hängt an jeden Namen die Herkunft an: über Labels definierte heißen intern
<code>sec-headers@docker</code>, aus Dateien geladene <code>sec-headers@file</code>. Innerhalb desselben Providers darfst
du den Zusatz weglassen – <strong>über Provider-Grenzen hinweg nicht.</strong> Genau daran scheitern die meisten
„middleware not found&quot;-Fehler.</div>
</div>
<h3 id="9-welche-middleware-für-welchen-dienst">9. Welche Middleware für welchen Dienst?</h3>
<p>Nicht jeder Dienst braucht alles. Die Kette, die zu einem Dienst passt, hängt davon ab, wer ihn
benutzen soll und was er selbst schon mitbringt:</p>
<table>
	<thead>
			<tr>
					<th>Diensttyp</th>
					<th>Beispiele</th>
					<th>sinnvolle Kette</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Öffentlich, für alle</td>
					<td>Website, Blog, Status-Seite</td>
					<td><code>sec-headers</code></td>
			</tr>
			<tr>
					<td>Öffentlich mit Login</td>
					<td>Nextcloud, Immich, Vaultwarden</td>
					<td><code>sec-headers</code>, <code>rate</code></td>
			</tr>
			<tr>
					<td>Nur für dich, mit Login</td>
					<td>Uptime Kuma, Grafana</td>
					<td><code>sec-headers</code>, <code>rate</code> (+ Allowlist, wenn möglich)</td>
			</tr>
			<tr>
					<td>Ohne eigene Anmeldung</td>
					<td>Traefik-Dashboard, Metrik-Endpunkte</td>
					<td><code>nur-ich</code>, <code>rate</code>, <code>auth</code></td>
			</tr>
			<tr>
					<td>Gefährlich bei Missbrauch</td>
					<td>Dockge, Portainer, Adminer</td>
					<td><code>nur-ich</code>, <code>auth</code> – oder gar nicht öffentlich</td>
			</tr>
	</tbody>
</table>
<p>Zwei Regeln dahinter: <strong>Kein Rate-Limit vor Dienste, die viele parallele Anfragen brauchen</strong> –
Immich beim Handy-Backup oder ein Medienserver beim Streamen laufen sonst in <code>429</code>. Und: <strong>BasicAuth
nie zusätzlich vor eine App mit eigener Anmeldung</strong>, wenn sie eine Mobil-App hat – die meisten Apps
können mit zwei übereinanderliegenden Anmeldungen nicht umgehen.</p>
<p>Für alles, was in der letzten Zeile steht, ist die ehrliche Antwort meist nicht „noch eine
Middleware&quot;, sondern: nicht ins offene Internet stellen und über
<a href="/tutorials/wireguard-vpn-einrichten/">WireGuard</a> erreichen.</p>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<div class="troubleshoot not-prose">
<p><strong>In den Logs steht „middleware … does not exist&quot; und der Dienst antwortet mit 404.</strong> Fast immer
fehlt der Provider-Zusatz: Eine im File-Provider definierte Middleware heißt aus Sicht der
Docker-Labels <code>name@file</code>. Ohne Zusatz sucht Traefik sie unter den Docker-Labels – und findet
nichts. Umgekehrt gilt dasselbe für <code>name@docker</code>.</p>
<p><strong>BasicAuth lehnt das richtige Passwort ab.</strong> Die <code>$</code>-Zeichen im bcrypt-Hash wurden von Docker
Compose als Variablen interpretiert. Im Compose müssen sie <strong>verdoppelt</strong> werden (<code>$$2y$$05$$…</code>).
Mit <code>docker compose config</code> siehst du den Wert, der wirklich ankommt.</p>
<p><strong>Das Rate-Limit scheint nicht zu greifen.</strong> Sequenzielle <code>curl</code>-Aufrufe sind zu langsam: Jeder
Prozess baut eine neue Verbindung auf, damit bleibst du unter dem Limit. Teste parallel, etwa mit
<code>seq 40 | xargs -P 8 -I{} curl …</code> – dann erscheinen die <code>429</code>.</p>
<p><strong>Nach dem Setzen der IP-Allowlist bekommst du selbst 403.</strong> Traefik sieht nicht deine IP, sondern
die des davorstehenden Proxys (Cloudflare, Load-Balancer). Prüfe mit <code>traefik/whoami</code>, was in
<code>X-Forwarded-For</code> steht, und arbeite bei mehrstufigen Aufbauten mit
<code>ipallowlist.ipstrategy.depth</code>.</p>
<p><strong>Eine v2-Konfiguration mit <code>ipWhiteList</code> funktioniert nach dem Upgrade nicht mehr.</strong> Die Middleware
heißt in Traefik v3 <code>ipAllowList</code>; der alte Name wurde entfernt, die Regel greift damit nicht mehr.
Beim Umstieg alle Vorkommen umbenennen.</p>
<p><strong>Der Browser kommt trotz korrigierter Konfiguration nicht mehr per HTTP durch.</strong> Das ist HSTS und
kein Fehler: Der Browser hat sich <code>max-age</code> gemerkt und erzwingt HTTPS selbst dann, wenn dein Server
es nicht mehr anbietet. Zum Testen hilft ein privates Fenster oder ein anderer Browser – und deshalb
die Warnung zu <code>preload</code> oben.</p>

</div>
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
  Dein Problem ist nicht dabei?
  <a href="/fehler/" class="font-medium text-paprika-700 hover:underline dark:text-paprika-400">Alle Fehlermeldungen durchsuchen →</a>
</p>

<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<p><strong>Nach jedem Traefik-Update gegenprüfen.</strong> Middlewares sind Konfiguration, und Konfiguration
veraltet: Zwischen v2 und v3 wurde <code>ipWhiteList</code> zu <code>ipAllowList</code>, andere Felder haben neue Namen
bekommen. Nach einem Update lohnt der kurze Durchlauf mit <code>curl -sI</code> – erscheinen die erwarteten
Header noch? Antwortet die Allowlist noch mit <code>403</code>? Zwei Minuten, die dir stille Wirkungslosigkeit
ersparen.</p>
<p><strong>Sichere das <code>dynamic</code>-Verzeichnis mit.</strong> Wenn du dem File-Provider folgst, steckt darin deine
gesamte Härtung. Es gehört zusammen mit <code>compose.yaml</code> und <code>acme.json</code> ins Backup – klein, aber
schmerzhaft zu rekonstruieren. Wie das automatisch läuft, steht in
<a href="/tutorials/backups-mit-restic/">Backups mit Restic</a>.</p>
<p><strong>Allowlists altern.</strong> Dynamische IP-Adressen von Privatanschlüssen ändern sich, Büro-Netze ziehen
um. Nimm dir einmal im Quartal die <code>sourcerange</code>-Einträge vor und wirf raus, was du nicht mehr
zuordnen kannst – eine Allowlist mit fremden Adressen ist schlechter als keine.</p>
<p><strong>Ehrlich zur Reichweite dieser Maßnahmen.</strong> Security-Header schützen den Browser deiner Nutzer,
nicht deinen Server. Ein Rate-Limit bremst Brute-Force, verhindert es nicht. BasicAuth ist eine Tür
mit einem Schloss, kein Benutzerkonzept. Was diese vier Middlewares <strong>nicht</strong> ersetzen: einen
<a href="/tutorials/ssh-absichern/">abgesicherten SSH-Zugang</a>, eine
<a href="/tutorials/firewall-ufw-einrichten/">Firewall</a>,
<a href="/tutorials/automatische-updates-unattended-upgrades/">automatische Updates</a> und
<a href="/tutorials/fail2ban-einrichten/">fail2ban</a> bzw. <a href="/tutorials/crowdsec-einrichten/">CrowdSec</a> für die
Ebene darunter. Die Middlewares sind die Schicht, die vor deinen Anwendungen sitzt – nicht die
einzige.</p>
]]></content:encoded></item></channel></rss>