Rate Limiting mit nginx, fail2ban und Cloudflare

Aktualisiert: 5 Min. Lesezeit Redaktion DDoS-Attacke
Kurz gesagt

Rate Limiting begrenzt, wie viele Anfragen ein Client in einem Zeitraum stellen darf, und weist den Rest mit HTTP 429 ab. Auf eigenen Servern übernimmt das nginx mit limit_req, fail2ban sperrt Dauerverursacher per Firewall, vor dem Server erledigen Dienste wie Cloudflare dasselbe am Rand des Netzes. Gegen große volumetrische Angriffe reicht es allein nicht.

Das erste Rate Limit, das ich auf einem Produktivserver gesetzt habe, hat zuverlässig funktioniert. Es hat nur den falschen getroffen: den eigenen Load Balancer. Alle Requests kamen von seiner IP, nach zehn pro Sekunde war für die ganze Welt Schluss. Rate Limiting ist simpel, aber es verzeiht keine falsche Annahme darüber, wer der Client ist.

Was Rate Limiting macht und was nicht

Rate Limiting zählt Anfragen pro Schlüssel, meist pro IP-Adresse, und lehnt alles ab, was über einem festgelegten Takt liegt. nginx arbeitet dabei nach dem Leaky-Bucket-Verfahren: Requests fließen in einen Eimer, der mit konstanter Rate abläuft. Läuft er über, wird abgewiesen.

Das hilft gut gegen:

  • Brute-Force auf Login und Passwort-Reset
  • Scraper und schlecht gebaute Bots
  • HTTP-Floods aus überschaubar vielen Quellen
  • Einzelne teure Endpunkte wie Suche, Export oder PDF-Erzeugung

Es hilft nicht gegen volumetrische Angriffe auf Netzwerkebene. Wenn 10 Gbit/s UDP auf eine 1-Gbit/s-Leitung treffen, kommt kein einziges Paket sinnvoll bei nginx an. Welche Angriffsarten auf welcher Ebene wirken, erklärt die Übersicht DDoS-Arten.

Startwerte: welche Grenze für welchen Pfad

Diese Werte sind Ausgangspunkte für eine typische Website mit Shop oder CMS, keine Normwerte. Prüfe sie gegen deine Logs.

BereichRate pro IPburstBegründung
Normale Seiten10r/s20Ein Seitenaufruf lädt oft 10+ Ressourcen, sofern nicht vom CDN ausgeliefert
Login, Passwort-Reset5r/m3Kein Mensch tippt sein Passwort 5-mal pro Minute
Suche, Filter2r/s5Jede Anfrage trifft die Datenbank
API (authentifiziert)nach Vertrag10–50Lieber pro API-Key als pro IP limitieren
Statische Dateienkein Limit–Gehören in den Cache, nicht in die Zählung

So bekommst du die 20 höchsten Werte für Requests pro IP und Minute aus dem Access-Log (Combined-Format):

awk '{split($4,t,":"); print $1, t[2]":"t[3]}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -20

Wenn dein aktivster normaler Besucher auf 90 Requests pro Minute kommt, ist 10r/s mit burst=20 reichlich Luft. Liegen Crawler oben, filtere sie vorher nach User-Agent heraus.

nginx limit_req und limit_conn einrichten

Die Grundlage ist das Modul ngx_http_limit_req_module, das in jeder Standard-nginx-Installation enthalten ist. Laut nginx-Dokumentation speichert eine Zone von 1 MB etwa 16.000 Zustände à 64 Byte oder 8.000 à 128 Byte. Auf 64-Bit-Systemen gilt der zweite Wert, 10 MB reichen also für rund 80.000 gleichzeitig verfolgte IPs.

http {
    # Whitelist: leerer Schlüssel wird nicht gezählt
    geo $limit {
        default        1;
        10.0.0.0/8     0;
        192.0.2.10/32  0;   # eigenes Monitoring
    }
    map $limit $limit_key {
        0 "";
        1 $binary_remote_addr;
    }

    limit_req_zone  $limit_key zone=general:10m rate=10r/s;
    limit_req_zone  $limit_key zone=login:10m   rate=5r/m;
    limit_conn_zone $limit_key zone=perip:10m;

    limit_req_status  429;
    limit_conn_status 429;
    limit_req_log_level warn;

    server {
        listen 443 ssl;
        server_name example.de;

        location / {
            limit_req  zone=general burst=20 nodelay;
            limit_conn perip 30;
            # ... proxy_pass oder try_files
        }

        location = /wp-login.php {
            limit_req zone=login burst=3 nodelay;
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        }
    }
}

Ein paar Details, die in Tutorials gern fehlen:

  • burst ohne nodelay verzögert überzählige Requests, statt sie sofort zu bedienen. Das fühlt sich für Nutzer wie eine langsame Seite an. Mit nodelay werden Requests bis zur burst-Grenze sofort bedient, danach abgewiesen.
  • delay= (seit nginx 1.15.7) ist der Mittelweg: Die ersten Requests über der Rate laufen sofort durch, ab dem delay-Wert wird gebremst.
  • Raten unter 1 pro Sekunde schreibst du in Requests pro Minute. 30r/m entspricht 0,5r/s.
  • Der Standardstatus ist 503, nicht 429. Ohne limit_req_status 429 sieht dein Monitoring Serverfehler, wo eigentlich Abwehr stattfindet.

Nach jeder Änderung: nginx -t && systemctl reload nginx.

Hinter Cloudflare oder Load Balancer: echte IP zuerst

Das ist die Falle aus der Einleitung. Hinter einem Proxy ist $binary_remote_addr die Adresse des Proxys. Mit dem Real-IP-Modul ersetzt nginx sie durch die echte Client-IP aus einem Header:

set_real_ip_from 173.245.48.0/20;   # alle Cloudflare-Bereiche eintragen
set_real_ip_from 2400:cb00::/32;
real_ip_header CF-Connecting-IP;

Vertraue diesem Header nur von den Proxy-Adressen. Sonst kann jeder Angreifer sich per gefälschtem Header eine beliebige IP geben. Wie das zusammen mit der Abschottung der Origin funktioniert, steht im Artikel zum DDoS-Schutz mit Cloudflare.

fail2ban: Wiederholungstäter in die Firewall

nginx weist ab, aber jeder abgewiesene Request kostet trotzdem eine TLS-Verbindung und etwas CPU. fail2ban liest das Error-Log, findet dort die Zeilen mit „limiting requests“ und sperrt IPs, die immer wieder ins Limit laufen, auf Firewall-Ebene. Der passende Filter nginx-limit-req ist bei fail2ban dabei. Den Jail legst du in /etc/fail2ban/jail.d/nginx-limit-req.local an:

[nginx-limit-req]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
findtime = 600
maxretry = 10
bantime  = 3600

Prüfen mit:

fail2ban-client status nginx-limit-req

Wichtig: Hinter Cloudflare bringt das wenig bis gar nichts. Die Firewall sieht nur Verbindungen von Cloudflare-Knoten. Sperrt fail2ban eine davon, trifft es zufällige echte Besucher, der Angreifer läuft über den nächsten Knoten weiter. In diesem Fall gehört die Sperre an den Rand, also in eine Cloudflare-Regel.

Rate Limiting bei Cloudflare und beim Hoster

Im Cloudflare-Free-Plan bekommst du genau eine Rate-Limiting-Regel. Sie zählt pro IP in einem Zeitraum von 10 Sekunden, kann nach Pfad filtern und blockiert für 10 Sekunden. Für einen einzelnen Login-Pfad reicht das. Pro erlaubt zwei Regeln, Business fünf. Der Vorteil gegenüber nginx: Die abgelehnten Requests erreichen deinen Server gar nicht erst.

Ein sinnvoller Aufbau hat deshalb zwei Schichten. Grobe Limits am Rand fangen Masse ab, feine Limits in nginx schützen die teuren Pfade. Läuft ein Angriff bereits, beginnt die Reihenfolge der Maßnahmen aber nicht beim Feintuning, sondern bei den Sofortmaßnahmen gegen DDoS.

Woran du merkst, dass dein Limit falsch sitzt

  • 429-Spitzen zur Mittagszeit aus deutschen Provider-Netzen: zu streng für normale Nutzer.
  • Kein einziger 429 während eines bekannten Angriffs: Schlüssel falsch (Proxy-IP) oder Pfad nicht erfasst.
  • Kundenbeschwerden aus großen Firmen oder Unis: geteilte NAT-IPs. Burst erhöhen oder Netz per geo ausnehmen.
  • Monitoring meldet Ausfälle: Monitoring-IPs in die Whitelist.

So zählst du die 429-Antworten pro Minute im Access-Log:

awk '$9 == 429 {split($4,t,":"); print t[2]":"t[3]}' /var/log/nginx/access.log | uniq -c | tail

Rate Limiting schützt die Anwendung. Die Domain selbst braucht anderen Schutz: Registrar-Lock, DNSSEC und Zugangsdaten mit zweitem Faktor. Was dazugehört, steht in den Ratgebern zur Domain-Sicherheit von betadomain. Für die Nameserver selbst lohnt sich ein zweiter, unabhängiger DNS-Anbieter.

Wenn du deine Domains ohnehin neu ordnest: Bei betadomain kannst du eine .de-Domain mit DNSSEC registrieren und die Nameserver frei verwalten, auch über die Domain-API für Entwickler.

Schritt für Schritt

  1. Normalverkehr messenAus dem nginx-Access-Log die Requests pro IP und Minute für einen normalen Tag auswerten und die Spitzenwerte echter Besucher notieren.
  2. Zonen in nginx anlegenIm http-Block limit_req_zone für allgemeinen Traffic und für den Login definieren, limit_req_status auf 429 setzen.
  3. Limits den Locations zuweisenlimit_req mit burst und nodelay in den passenden location-Blöcken eintragen, nginx -t ausführen und neu laden.
  4. Echte Client-IP sicherstellenHinter Cloudflare oder einem Load Balancer real_ip_header und set_real_ip_from setzen, sonst teilen sich alle Besucher die IP des Proxys.
  5. fail2ban für DauerverursacherDen Jail nginx-limit-req aktivieren, damit IPs, die wiederholt ins Limit laufen, für eine Stunde in der Firewall landen.
  6. Wirkung prüfenMit fail2ban-client status und den 429-Zahlen im Access-Log kontrollieren, ob die Regeln Angreifer treffen und nicht Kunden.

Domain gefunden? Prüfen und registrieren kannst du sie direkt bei Domain ohne Lockvogelpreis – Jahrespreis ab 2,99 €.

Häufige Fragen

Welcher Statuscode ist bei Rate Limiting richtig?

HTTP 429 Too Many Requests, definiert in RFC 6585. nginx liefert ohne Anpassung 503, was Monitoring und Suchmaschinen als Serverfehler deuten. Mit limit_req_status 429 stellst du das um und kannst per Retry-After-Header sagen, wann der Client es erneut versuchen soll.

Sperrt Rate Limiting Nutzer hinter einer Firmen-NAT aus?

Das kann passieren, weil sich viele Menschen eine öffentliche IP teilen. Setze die Grenzen für normale Seiten großzügig, nutze burst und nimm bekannte Partnernetze per geo-Block aus. Streng limitieren solltest du nur Login, Passwort-Reset und teure Endpunkte.

Hilft Rate Limiting gegen DDoS?

Gegen HTTP-Floods aus wenigen Quellen und gegen teure Requests ja. Gegen einen verteilten Angriff aus zehntausenden IPs oder gegen volumetrische UDP-Floods nicht, weil die Leitung voll ist, bevor nginx überhaupt etwas sieht. Dafür brauchst du Filterung beim Provider oder einem CDN.

Wie finde ich den richtigen Grenzwert?

Aus deinen eigenen Logs. Ermittle, wie viele Requests pro Minute die aktivsten normalen Besucher stellen, und setze die Grenze deutlich darüber. Starte im Logmodus oder mit großzügigem burst und verschärfe erst, wenn du siehst, wen die Regel trifft.