Nginx auf HestiaCP härten: Layer-7-DDoS-Angriffe abwehren und dynamische Inhalte mit Caching beschleunigen

Wie man die Nginx-Templates von HestiaCP anpasst, um Anfragen zu begrenzen, langsame Verbindungen zu blockieren und FastCGI Cache zu aktivieren – für weniger Schaden durch Layer-7-DDoS-Angriffe und schnellere dynamische Seiten.

Nginx auf HestiaCP härten: Layer-7-DDoS-Angriffe abwehren und dynamische Inhalte mit Caching beschleunigen

Wer einen Server mit HestiaCP betreibt, stößt früher oder später auf ein bekanntes Problem: Die Website reagiert nur noch träge, die CPU-Auslastung schießt in die Höhe, und die Nginx-Logs füllen sich mit Tausenden von Anfragen, die für sich genommen harmlos aussehen. Oft handelt es sich dabei nicht um einen "guten" Traffic-Anstieg, sondern um einen Angriff auf Anwendungsebene (Layer 7 DDoS): eine Angriffsart, die nicht versucht, die Netzwerkbandbreite zu sättigen, sondern die Serverressourcen (CPU, Arbeitsspeicher, Datenbankverbindungen) durch HTTP-Anfragen erschöpft, die völlig legitim wirken. In diesem Leitfaden arbeiten wir direkt an den Nginx-Templates von HestiaCP, um Schutzmaßnahmen gegen diese Art von Angriffen hinzuzufügen, und nutzen gleichzeitig das Caching von Nginx, um dynamische Seiten (zum Beispiel von PHP erzeugte Seiten) zu entlasten – so wird die Website auch bei sehr hohem, aber legitimem Traffic widerstandsfähiger.

Was ein "Layer-7"-DDoS-Angriff ist und warum er Nginx betrifft

DDoS-Angriffe (Distributed Denial of Service) lassen sich je nach betroffener Schicht des OSI-Netzwerkmodells in verschiedene Kategorien einteilen. Angriffe auf Schicht 3 und 4 (Netzwerk und Transport) versuchen, die Bandbreite oder die Verbindungstabellen zu sättigen, und werden in der Regel vor dem Server gefiltert, etwa durch den Hosting-Anbieter oder einen Schutzdienst wie ein CDN. Angriffe auf Schicht 7 (die "Anwendungsschicht", auf der das HTTP-Protokoll lebt) sind heimtückischer, weil die einzelnen Anfragen kaum von denen eines echten Nutzers zu unterscheiden sind: Eine Anfrage an eine Login-Seite, eine Suche auf der Website oder einen Endpunkt, der die Datenbank abfragt, tausendfach pro Sekunde von vielen verschiedenen IP-Adressen wiederholt, kann einen Server in die Knie zwingen, selbst wenn die Netzwerkverbindung bei weitem nicht ausgelastet ist.

Da Nginx in einer HestiaCP-Installation jede HTTP-Anfrage als Erstes empfängt (egal ob es allein läuft oder als Reverse Proxy vor Apache), ist es der ideale Ort, um erste Gegenmaßnahmen umzusetzen: Es kann begrenzen, wie viele Anfragen es von einer einzelnen IP-Adresse akzeptiert, wie viele gleichzeitige Verbindungen es offenhält, und es kann einen Teil des Traffics "abfangen", indem es Antworten aus dem Cache liefert, statt sie jedes Mal neu zu erzeugen.

Warum man die Original-Templates von HestiaCP nicht bearbeiten sollte

In HestiaCP wird die Nginx-Konfiguration jeder Domain nicht von Hand geschrieben: Sie wird automatisch aus Vorlagendateien generiert, den sogenannten Templates, die Platzhalter wie %domain%, %user%, %docroot% oder %backend_lsnr% enthalten, die beim Erstellen oder Neuaufbau der Konfiguration durch die tatsächlichen Werte der Website ersetzt werden. Je nach Szenario liegen die Templates an zwei Stellen auf dem Server:

  • Nginx als einziger Webserver (mit PHP-FPM): /usr/local/hestia/data/templates/web/nginx/php-fpm/
  • Nginx als Reverse Proxy vor Apache: /usr/local/hestia/data/templates/web/nginx/

Die offizielle HestiaCP-Dokumentation rät ausdrücklich davon ab, die Standard-Template-Dateien (zum Beispiel default.tpl) zu bearbeiten: Bei jedem Update des Panels werden diese Dateien überschrieben, und Änderungen würden verloren gehen. Der richtige Weg ist, das Template, das man anpassen möchte, unter einem neuen Namen zu kopieren und nur die Kopien zu bearbeiten. Für jedes Template braucht man in der Regel mehrere Dateien mit demselben Basisnamen, aber unterschiedlichen Endungen, zum Beispiel hardened.tpl, hardened.stpl (die SSL-Variante) und, falls vorhanden, das zugehörige Skript hardened.sh.

Das benutzerdefinierte Template Schritt für Schritt erstellen

Nach der Verbindung zum Server per SSH als root (oder mit einem Benutzer mit entsprechenden Rechten) erstellt man ein Template auf Basis des Standard-Templates so:

cd /usr/local/hestia/data/templates/web/nginx/php-fpm/
cp default.tpl hardened.tpl
cp default.stpl hardened.stpl

Jetzt sind hardened.tpl und hardened.stpl zwei unabhängige Kopien, die sich gefahrlos bearbeiten lassen, ohne dass die Änderungen beim nächsten HestiaCP-Update verloren gehen. Die folgenden Abschnitte zeigen, was in diese Dateien eingefügt werden muss, um die beiden wichtigsten Schutzmaßnahmen zu erhalten: Anfragenbegrenzung und Caching.

Anfragen pro IP-Adresse begrenzen: limit_req

Die erste und wirksamste Gegenmaßnahme gegen einen Angriff auf Anwendungsebene besteht darin, eine einzelne IP-Adresse daran zu hindern, in kurzer Zeit zu viele Anfragen zu senden. Nginx bietet dafür das Modul limit_req, das in zwei Schritten funktioniert: Zuerst definiert man eine "Shared-Memory-Zone", die Anfragen pro IP verfolgt, dann wendet man diese Zone auf einen oder mehrere location-Blöcke der Konfiguration an.

Die Zonendefinition gehört in den globalen http-Block von Nginx (Datei /etc/nginx/nginx.conf oder eine Datei in /etc/nginx/conf.d/, die automatisch eingebunden wird), nicht in das Template der einzelnen Domain:

# /etc/nginx/conf.d/ratelimit.conf
limit_req_zone $binary_remote_addr zone=generic:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;

Hier ist generic eine Zone, die im Durchschnitt 30 Anfragen pro Minute pro IP für die gesamte Website erlaubt, während login eine strengere Zone (10 Anfragen pro Minute) ist, die speziell für Authentifizierungsseiten gedacht ist, dem typischen Lieblingsziel dieser Angriffsart. Der Wert 10m gibt an, wie viel Speicher reserviert wird, um IP-Adressen zu verfolgen: 10 MB reichen für etwa 160.000 unterschiedliche Adressen, mehr als genug für die meisten Websites.

Anschließend wendet man im Template hardened.tpl die generische Zone auf die gesamte Website und die strengere Zone auf die Login-Seite an:

server {
    # ... von HestiaCP automatisch generierte Direktiven ...

    limit_req zone=generic burst=15 nodelay;

    location = /wp-login.php {
        limit_req zone=login burst=5 nodelay;
        include /etc/nginx/fastcgi_params;
        fastcgi_pass %backend_lsnr%;
    }
}

Der Parameter burst erlaubt kurze Spitzen legitimer Anfragen (zum Beispiel wenn ein Browser mehrere Ressourcen parallel lädt), ohne den Besucher zu blockieren, während nodelay verhindert, dass überschüssige Anfragen in eine Warteschlange gestellt werden, und stattdessen sofort einen 503 Service Temporarily Unavailable-Fehler an alle zurückgibt, die das Limit überschreiten. So erhält ein Bot, der die Login-Seite bombardiert, schnell Fehlermeldungen und verbraucht keine Serverressourcen mehr, während ein normaler Besucher nichts davon merkt.

Gleichzeitige Verbindungen begrenzen: limit_conn

Eine ergänzende Technik besteht darin, zu begrenzen, wie viele Verbindungen eine einzelne IP gleichzeitig offenhalten kann – nützlich gegen Angriffe, die viele Verbindungen öffnen und so lange wie möglich belegt halten. Auch hier funktioniert die Logik in zwei Schritten:

# /etc/nginx/conf.d/ratelimit.conf
limit_conn_zone $binary_remote_addr zone=perip:10m;
server {
    limit_conn perip 20;
}

Mit dieser Konfiguration kann eine einzelne IP-Adresse nicht mehr als 20 gleichzeitig offene Verbindungen zu dieser Website haben: Oberhalb dieser Schwelle antwortet Nginx mit einem Fehler, ohne die Anfrage überhaupt an das PHP-Backend oder Apache weiterzugeben, was wertvolle Ressourcen spart.

Schutz vor langsamen Verbindungen (Slowloris)

Eine Familie von Angriffen auf Anwendungsebene, bekannt als Slowloris, sendet nicht viele Anfragen, sondern öffnet Verbindungen und hält sie beschäftigt, indem sie Daten extrem langsam, tröpfchenweise sendet, um die maximale Anzahl an Verbindungen, die der Server verwalten kann, zu erschöpfen. Die Gegenmaßnahme besteht darin, enge Zeitlimits für den Empfang von Anfrage-Headern und -Body festzulegen, damit Nginx zu langsame Verbindungen schließt:

server {
    client_header_timeout 10s;
    client_body_timeout 10s;
    send_timeout 10s;
}

Die Standardwerte von Nginx für diese Timeouts liegen bei 60 Sekunden – mehr als genug Zeit für einen Slowloris-Angriff, um die verfügbaren Verbindungen zu sättigen. Eine Absenkung auf 10 Sekunden (ein Wert, der an die durchschnittliche Verbindungsqualität der echten Besucher der Website angepasst werden kann) verkürzt das Angriffsfenster drastisch, ohne für Nutzer mit normaler Verbindung spürbare Auswirkungen zu haben.

Verdächtige IPs und User-Agents blockieren

Wenn die Log-Analyse während eines Angriffs wiederkehrende IP-Adressen oder einen ungewöhnlichen User-Agent-String des bösartigen Traffics aufzeigt (viele einfache Bots verwenden einen leeren oder offensichtlich gefälschten User-Agent), kann man im Template eine direkte Sperre einfügen:

server {
    # Einen IP-Bereich blockieren
    deny 203.0.113.0/24;

    # Anfragen mit leerem oder verdächtigem User-Agent blockieren
    if ($http_user_agent ~* "^$|curl|wget|python-requests") {
        return 403;
    }
}

Diese Technik sollte mit Vorsicht eingesetzt werden: Das Blockieren nach User-Agent hilft nur gegen die primitivsten Bots, da ausgefeiltere Bots den User-Agent eines echten Browsers perfekt nachahmen; dennoch ist es ein nahezu kostenloser Filter, der einen Teil des unerwünschten Traffics eliminieren kann, bevor er den Rest der Konfiguration erreicht.

Dynamische Inhalte mit FastCGI Cache beschleunigen

Die bisher genannten Schutzmaßnahmen reduzieren den bösartigen Traffic, aber die zweite Säule der Resilienz besteht darin, dafür zu sorgen, dass legitimer Traffic (auch wenn er sehr hoch ist, etwa bei einem organischen Besucheranstieg) den Server so wenig wie möglich belastet. Für PHP-basierte Websites (einschließlich WordPress), die mit HestiaCP über Nginx und PHP-FPM betrieben werden, ist die Lösung der in Nginx integrierte FastCGI Cache: Beim ersten Aufruf einer Seite erzeugt Nginx sie wie gewohnt, indem es die Anfrage an PHP-FPM weiterleitet, speichert dabei aber eine Kopie; nachfolgende Anfragen an dieselbe Seite werden direkt aus dem Cache bedient, ohne PHP oder die Datenbank überhaupt zu involvieren – mit enormem Geschwindigkeitsgewinn und fast keiner Serverlast.

Auch hier muss die Cache-Zone global definiert werden:

# /etc/nginx/conf.d/fastcgi-cache.conf
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=WPCACHE:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

Dann sieht HestiaCP im Template hardened.tpl einen spezifischen Mechanismus vor: Direkt nach dem include fastcgi_params im Block, der PHP-Dateien verarbeitet, fügt man einen Verweis auf die domänenspezifische Konfigurationsdatei ein, die das Panel automatisch generiert, sobald der Cache über die Oberfläche aktiviert wird:

location ~ [^/]\.php(/|$) {
    fastcgi_pass %backend_lsnr%;
    fastcgi_index index.php;
    include /etc/nginx/fastcgi_params;
    include %home%/%user%/conf/web/%domain%/nginx.fastcgi_cache.conf*;

    fastcgi_cache WPCACHE;
    fastcgi_cache_valid 200 60m;
    fastcgi_cache_use_stale updating error timeout invalid_header http_500;
    add_header X-FastCGI-Cache $upstream_cache_status;
}

Der Header X-FastCGI-Cache ist zur Überprüfung sehr nützlich: Er erscheint in der HTTP-Antwort mit dem Wert HIT, wenn die Seite aus dem Cache bedient wird, und MISS, wenn sie von PHP erzeugt und erstmals zwischengespeichert wird; man kann ihn einfach im Browser (Entwicklertools, Netzwerk-Tab) oder im Terminal mit curl -I https://ihreseite.de/ prüfen. Die Option fastcgi_cache_use_stale mit dem Parameter updating ist bei einem Traffic-Spike besonders wichtig: Läuft der Cache-Eintrag einer Seite genau in dem Moment ab, in dem Hunderte gleichzeitiger Anfragen eintreffen, sendet Nginx nur eine davon an PHP, um sie neu zu erzeugen, und bedient alle anderen weiterhin mit der (leicht veralteten) vorherigen Version, bis die neue bereit ist – so wird verhindert, dass alle Anfragen gleichzeitig auf das Backend treffen.

Nicht jede Seite sollte zwischengespeichert werden: Der Administrationsbereich eines CMS oder Seiten mit personalisierten Inhalten für den angemeldeten Nutzer müssen ausgeschlossen werden, sonst könnte ein Nutzer die Daten eines anderen zu sehen bekommen. HestiaCP behandelt diese Fälle bereits teilweise über die vom Panel generierte Datei nginx.fastcgi_cache.conf, bei bestimmten CMS wie WordPress sollte man aber prüfen, ob Cookies wie Sitzungs- oder Warenkorb-Cookies den Cache für diese Anfrage korrekt über eine cookie-basierte fastcgi_cache_bypass-Direktive deaktivieren.

Das neue Template anwenden und das Ergebnis überprüfen

Sobald hardened.tpl und hardened.stpl fertig sind, erfolgt die Aktivierung für eine Domain vollständig über die Oberfläche von HestiaCP:

  1. Im Panel anmelden, Web > [Domainname] > Edit öffnen.
  2. Im Dropdown-Menü des Nginx-Templates das neue Template hardened auswählen (es erscheint automatisch in der Liste, sobald die Dateien kopiert wurden).
  3. Speichern: HestiaCP generiert die Konfiguration der Domain aus dem neuen Template neu.

Alternativ erreicht man dasselbe Ergebnis über die Kommandozeile mit:

v-change-web-domain-tpl benutzer domain.de hardened
v-rebuild-web-domain benutzer domain.de yes

wobei das letzte Parameter yes auch einen Neustart der beteiligten Webdienste erzwingt. Vor der Anwendung der Änderung in der Produktion sollte man die Konfigurationssyntax immer mit nginx -t überprüfen, das eventuelle Fehler meldet, ohne den laufenden Dienst zu unterbrechen, und das Verhalten von Cache und Anfragenbegrenzung auf einer Testdomain oder in einem Zeitraum mit geringem Traffic testen.

Um den FastCGI-Cache nach einer Änderung an der Website zu löschen (zum Beispiel nach einem Theme- oder Inhaltsupdate), bietet HestiaCP auf der Bearbeitungsseite der Domain die Schaltfläche "Purge Nginx Cache" oder den Befehl v-purge-nginx-cache benutzer domain.de über die Kommandozeile.

Fazit

Keine der in diesem Leitfaden beschriebenen Einzelmaßnahmen ist für sich allein ein vollständiger Schutz gegen einen ernsthaften, anhaltenden Angriff auf Anwendungsebene: Dafür braucht es dedizierte Lösungen vor dem Server, etwa ein CDN mit DDoS-Schutz oder eine Web Application Firewall. Die Kombination aus Anfragenbegrenzung, Verbindungsbegrenzung, engen Timeouts für langsame Clients und einem gut konfigurierten FastCGI-Cache auf den Nginx-Templates von HestiaCP hebt jedoch deutlich die Schwelle an bösartigem Traffic an, die der Server allein abfangen kann, und verbessert gleichzeitig die Website-Leistung bei hohem, legitimem Traffic. Es ist die Art von Maßnahme mit "geringem Aufwand, hohem Nutzen", die sich auf jedem öffentlich im Internet erreichbaren HestiaCP-Server lohnt.

Offizielle Quellen

Teilen

Was ist Ihre Reaktion?

Gefällt mir Gefällt mir 0
Gefällt mir nicht Gefällt mir nicht 0
Liebe Liebe 0
Lustig Lustig 0
Wütend Wütend 0
Traurig Traurig 0
Wow Wow 0