Ein öffentlich erreichbarer Git-Server, eine dauerhaft hohe CPU-Auslastung und hunderttausende automatisierte Zugriffe aus weltweit verteilten Netzen. Auf unserem Forgejo-System führte genau diese Kombination dazu, dass die CPU über längere Zeit teilweise nahe bei 100 Prozent ausgelastet war.
Dank der Überwachung mit Checkmk war schnell sichtbar, dass hier etwas nicht stimmte. Die eigentliche Ursache zeigte sich anschliessend in den Nginx-Access-Logs: Eine enorme Menge automatisierter Clients rief gezielt aufwendige Forgejo-Seiten wie Commit-, Source- und insbesondere Blame-Ansichten auf.
Was zunächst nach gewöhnlichem Bot-Traffic aussah, entwickelte sich zu einem ziemlich eindrücklichen Praxisbeispiel dafür, was die Kombination aus Monitoring, Log-Analyse, Nginx, Fail2Ban und nftables leisten kann.
Checkmk machte das Problem sichtbar
Der erste Hinweis kam aus dem Monitoring. Checkmk zeigte über längere Zeit eine ungewöhnlich hohe CPU-Auslastung. Teilweise bewegte sich der Server über Stunden zwischen rund 80 und 100 Prozent CPU-Last.
Damit war zwar klar, dass ein Problem bestand, aber noch nicht, wodurch es verursacht wurde. Genau hier zeigt sich der Wert eines Monitorings mit historischen Messwerten: Es ist nicht nur sichtbar, dass ein System gerade langsam ist. Man kann nachvollziehen, wann ein Problem begonnen hat, wie es sich entwickelt und später auch überprüfen, ob eine Gegenmassnahme tatsächlich funktioniert.
Auf dem Server selbst zeigte sich schliesslich, dass eine grosse Zahl von Git-Prozessen ausgeführt wurde. Besonders auffällig waren Aufrufe von git blame --porcelain.
Der nächste Schritt führte deshalb ins Nginx-Access-Log.
Hunderttausende automatisierte Zugriffe auf Forgejo
Im Access-Log zeigte sich ein klares Muster. Automatisierte Clients riefen in hoher Frequenz unter anderem Forgejo-Pfade wie diese auf:
/Checkmk/Checkmk/blame/commit/...
/Checkmk/Checkmk/src/commit/...
Gerade Blame-Ansichten können vergleichsweise aufwendig sein, weil dafür Informationen aus der Git-Historie ermittelt werden müssen. Bei normalen Zugriffszahlen ist das kein Problem. Wenn jedoch hunderte solcher Requests pro Minute eintreffen, sieht die Situation anders aus.
Die Zugriffe kamen dabei nicht einfach von einigen wenigen Servern. Die Requests waren über eine enorme Anzahl verschiedener öffentlicher IP-Adressen, Provider, Mobilfunknetze und Cloud-Plattformen verteilt.
Ein einzelnes auffälliges Netz liess sich zunächst tatsächlich identifizieren und sperren. Kurz darauf kamen die gleichen Zugriffe jedoch von völlig anderen Adressen weiter.
Die teuren Requests möglichst früh stoppen
Der wichtigste Schritt für die unmittelbare Entlastung bestand darin, problematische Requests möglichst früh abzufangen.
Wenn bereits Nginx entscheiden kann, dass ein bestimmter Request nicht an Forgejo weitergereicht werden soll, muss Forgejo dafür keine weitere Verarbeitung durchführen. Insbesondere bei den zuvor beobachteten Blame-Aufrufen verhindert das, dass unnötig Git-Prozesse gestartet werden.
Die Auswirkung war im Checkmk-Monitoring praktisch unmittelbar sichtbar. Die zuvor dauerhaft hohe CPU-Auslastung brach ein und stabilisierte sich anschliessend auf einem wesentlich niedrigeren Niveau.
Damit war das eigentliche Lastproblem bereits deutlich entschärft. Die Crawler verschwanden dadurch allerdings nicht. Sie fragten weiterhin dieselben und weitere Pfade ab.
Der nächste Schritt bestand deshalb darin, die auffälligen Clients automatisiert zu erkennen und zu sperren.
Fail2Ban nicht nur für SSH
Fail2Ban wird häufig hauptsächlich als Schutz gegen fehlgeschlagene SSH-Anmeldungen eingesetzt. Tatsächlich ist das Konzept wesentlich allgemeiner.
Fail2Ban überwacht Logs anhand definierter Filter. Wird ein bestimmtes Verhalten innerhalb eines festgelegten Zeitraums ausreichend oft erkannt, kann eine Aktion ausgelöst werden. Das kann beispielsweise eine Sperre der betreffenden IP-Adresse über die lokale Firewall sein.
Damit eignet sich Fail2Ban auch sehr gut für Webserver und individuelle Anwendungen, sofern sich unerwünschtes Verhalten zuverlässig in den Logs erkennen lässt.
Wir haben deshalb mehrere Jails mit unterschiedlichen Aufgaben eingesetzt, statt einfach jeden HTTP-Fehler als Angriff zu behandeln.
forgejo-crawler: gezielt gegen die beobachteten Requests
Der wichtigste Jail für diesen konkreten Vorfall ist forgejo-crawler.
Der dazugehörige Filter wertet das Nginx-Access-Log aus und erkennt gezielt die auffälligen Zugriffe auf die betroffenen Forgejo-Pfade. Damit wird nicht pauschal jeder Besucher gesperrt, der einmal einen HTTP-404 erzeugt.
Gerade das war uns wichtig. Ein 404 kann durch einen veralteten Link, einen Tippfehler oder eine normale Anwendung entstehen. Daraus automatisch einen Angriff abzuleiten wäre unnötig aggressiv.
Beim beobachteten Crawler-Verhalten war die Situation hingegen eindeutig genug, um sehr restriktiv reagieren zu können.
Der Jail wurde unter anderem mit folgenden Parametern betrieben:
[forgejo-crawler]
enabled = true
filter = forgejo-crawler
logpath = /var/log/nginx/access.log
port = http,https
protocol = tcp
backend = auto
findtime = 1h
maxretry = 1
bantime = 7d
Besonders auffällig ist dabei maxretry = 1. Normalerweise würden wir bei Fail2Ban mehrere Versuche zulassen. Bei diesem konkreten Muster wäre das allerdings wenig hilfreich gewesen.
Der Crawler verteilte seine Requests über eine riesige Anzahl verschiedener IP-Adressen. Viele Adressen tauchten nur sehr wenige Male auf. Würde Fail2Ban beispielsweise fünf oder zehn Treffer von derselben Adresse abwarten, würde ein grosser Teil der beteiligten Systeme niemals gesperrt.
Die lange Bannzeit von sieben Tagen sorgt gleichzeitig dafür, dass bereits erkannte Adressen nicht kurze Zeit später wieder von vorne beginnen können.
Eigene interne Netze und vertrauenswürdige Systeme sind über ignoreip explizit von den Sperren ausgenommen.
nginx-forgejo-scanner: klassische Webscanner erkennen
Parallel dazu verwenden wir einen eigenen nginx-forgejo-scanner-Jail.
Dieser hat eine andere Aufgabe. Er sucht nicht nach dem speziellen Forgejo-Crawler-Muster, sondern nach typischen automatisierten Scans gegen Webserver.
Dazu gehören beispielsweise Requests auf Pfade wie:
/wp-admin/
/wp-login.php
/xmlrpc.php
/phpmyadmin/
/.env
/.git/
/vendor/phpunit/
/cgi-bin/
/server-status
Auf einem Forgejo-Server gibt es normalerweise wenig plausible Gründe, innerhalb kurzer Zeit WordPress, phpMyAdmin, PHPUnit, versteckte Git-Verzeichnisse und Konfigurationsdateien abzuklopfen.
Ein einzelner solcher Request muss trotzdem noch kein Grund für eine Sperre sein. Der Scanner-Jail kann deshalb mehrere entsprechende Zugriffe innerhalb eines definierten Zeitraums sammeln und erst danach reagieren.
Damit lassen sich die offensichtlichen automatisierten Webscanner aussortieren, ohne jede falsche URL sofort mit einer mehrtägigen Sperre zu beantworten.
nginx-botsearch als zusätzliche Schutzschicht
Zusätzlich kommt nginx-botsearch zum Einsatz.
Damit ergänzen wir unsere eigenen Forgejo-spezifischen Regeln um allgemeine Erkennungsmuster für automatisierte Such- und Scanversuche gegen den Webserver.
Der Vorteil dieser Aufteilung besteht darin, dass nicht ein riesiger eigener Filter sämtliche denkbaren Angriffe erkennen muss. Allgemeine Muster können über bestehende Fail2Ban-Mechanismen behandelt werden, während sich unsere eigenen Filter auf das Verhalten konzentrieren, das für die konkrete Umgebung relevant ist.
nginx-limit-req für wiederholt auffällige Clients
Eine weitere Schutzschicht bildet das Rate Limiting von Nginx.
Nginx kann die Anzahl von Requests begrenzen, die ein Client innerhalb eines bestimmten Zeitraums ausführen darf. Wird diese Grenze überschritten, entstehen entsprechende Einträge im Nginx-Error-Log.
Der Fail2Ban-Jail nginx-limit-req kann diese Meldungen wiederum auswerten.
Damit entsteht eine sinnvolle Eskalation: Nginx begrenzt zunächst übermässigen Traffic. Clients, die wiederholt in diese Begrenzung laufen, können anschliessend durch Fail2Ban vollständig gesperrt werden.
recidive für Wiederholungstäter
Mit recidive kommt noch eine weitere Ebene hinzu.
Dieser Jail überwacht nicht direkt das Nginx- oder Forgejo-Log, sondern die Aktivitäten von Fail2Ban selbst. Damit lassen sich IP-Adressen erkennen, die wiederholt durch andere Jails gesperrt werden.
Eine Adresse kann beispielsweise zunächst wegen eines Webscans gesperrt werden. Taucht sie nach Ablauf der Sperre erneut auf und wird wieder durch einen Jail erkannt, kann recidive darauf mit einer längeren Sperre reagieren.
Damit wird zwischen einem einzelnen Vorfall und wiederholt auffälligem Verhalten unterschieden.
nftables übernimmt die eigentliche Sperre
Fail2Ban selbst ist dabei nicht die Firewall. Es analysiert die Logs und entscheidet anhand der definierten Regeln, wann eine Adresse gesperrt werden soll.
Die eigentliche Durchsetzung übernimmt auf unserem System nftables.
Das erwies sich bei diesem Vorfall als besonders relevant, denn die Zahl der gleichzeitig gesperrten Adressen erreichte eine Grössenordnung, die wir bei der Einrichtung definitiv nicht als normalen Dienstag eingeplant hatten.
Zeitweise befanden sich mehr als 600’000 IP-Adressen gleichzeitig im Forgejo-Crawler-Jail.
Solche Mengen möchte man nicht als hunderttausende voneinander unabhängige klassische Firewall-Regeln verwalten. nftables kann IP-Adressen stattdessen effizient in Sets verwalten. Eine Firewall-Regel prüft dann, ob sich die Quelladresse in einem solchen Set befindet.
Damit blieb die eigentliche Paketfilterung auch mit einer sehr grossen Anzahl gebannter IP-Adressen effizient.
Über 600’000 gleichzeitig gesperrte IP-Adressen
Das tatsächliche Ausmass des Crawlers wurde erst durch die Fail2Ban-Statistiken richtig sichtbar.
Die Zahl der gleichzeitig gebannten IP-Adressen stieg innerhalb kurzer Zeit massiv an und erreichte einen Peak von über 600’000 Adressen.
Gleichzeitig zeigte die Analyse der Netze, dass die Requests tatsächlich stark verteilt waren. Neben Cloud-Plattformen tauchten klassische Internetprovider, Mobilfunknetze und Anschlüsse aus zahlreichen Ländern auf.
Einzelne Netzbereiche zu blockieren hätte das Problem deshalb nicht gelöst. Sobald ein Bereich gesperrt war, kamen weitere Requests über andere Adressen.
Genau für dieses Verhalten erwies sich die automatische Kombination aus Log-Auswertung und dynamischer Firewall-Sperre als wesentlich sinnvoller.
Der entscheidende Wert war nicht die Zahl der Bans
600’000 gesperrte IP-Adressen sehen in einem Monitoring-Graphen natürlich spektakulär aus. Für den Betrieb des Systems ist diese Zahl allein aber eigentlich nicht besonders interessant.
Entscheidend ist, ob sich dadurch das ursprüngliche Problem verbessert.
Und genau das war im Checkmk-Monitoring sehr deutlich sichtbar.
Vor den Gegenmassnahmen lag die CPU-Auslastung des Forgejo-Servers über längere Zeit nahe bei 100 Prozent. Nach den Anpassungen von Nginx und der Aktivierung der Fail2Ban-Regeln fiel sie drastisch ab und stabilisierte sich auf einem niedrigen Niveau.
Auch der Speicherverbrauch beruhigte sich. Während der intensiven Crawling-Phase waren deutliche Schwankungen sichtbar. Nach der Entlastung bewegte sich das System wieder in einem normalen Bereich.
Gleichzeitig wurde das Nginx-Access-Log wesentlich ruhiger.
Checkmk überwacht auch Fail2Ban
Für uns war deshalb naheliegend, nicht nur Forgejo und das Betriebssystem mit Checkmk zu überwachen, sondern auch Fail2Ban selbst.
Die eingesetzte Erweiterung für Checkmk ist von Andreas Dvorak CheckMK Plugin Fail2Ban
Damit sehen wir direkt im Monitoring, wie viele Adressen aktuell durch einen Jail gesperrt sind und wie sich diese Zahl über die Zeit entwickelt.
Beim forgejo-crawler-Jail war dadurch sehr schön sichtbar, wie die Zahl der gesperrten IP-Adressen zunächst auf über 600’000 anstieg und anschliessend wieder kontinuierlich zurückging.
Das ist besonders interessant, weil die Bannzeit weiterhin sieben Tage beträgt. Ein erheblicher Teil der angezeigten Adressen waren damit bestehende Sperren, die erst nach und nach ausliefen.
Während die Zahl der aktiven Bans sank, blieb die CPU-Auslastung niedrig und das Access-Log ruhig. Damit konnten wir nachvollziehen, dass nicht einfach nur eine riesige Blockliste aufgebaut wurde, sondern der problematische Traffic tatsächlich deutlich zurückgegangen war.
Eine Schutzkette statt einer einzelnen Lösung
Am Ende bestand die Lösung nicht aus einem einzelnen Fail2Ban-Filter. Mehrere Komponenten übernehmen jeweils eine klar definierte Aufgabe:
- Checkmk erkennt ungewöhnliche Last und zeigt die Entwicklung über längere Zeit.
- Nginx liefert die relevanten Logs, verarbeitet die tatsächlichen Client-Adressen hinter dem Load Balancer und kann problematische Requests bereits vor Forgejo abfangen.
- forgejo-crawler reagiert gezielt auf das beobachtete Crawler-Verhalten.
- nginx-forgejo-scanner erkennt typische automatisierte Webscanner.
- nginx-botsearch ergänzt die Erkennung allgemeiner Bot- und Scanmuster.
- nginx-limit-req verbindet das Nginx-Rate-Limiting mit einer automatischen Sperre wiederholt auffälliger Clients.
- recidive erkennt Systeme, die wiederholt durch andere Fail2Ban-Jails auffallen.
- nftables setzt die eigentlichen IP-Sperren effizient auf Netzwerkebene um.
Der interessante Teil daran ist weniger ein einzelnes Werkzeug als deren Zusammenspiel. Das Monitoring machte zunächst sichtbar, dass überhaupt ein Problem bestand. Die Analyse der Prozesse und Logs führte zur Ursache. Nginx sorgte für die unmittelbare Entlastung, Fail2Ban automatisierte die weitere Erkennung und nftables setzte die Sperren um. Checkmk zeigte anschliessend wiederum, ob die Massnahmen tatsächlich funktionierten.
System Management bedeutet für uns genau das
Dieses Beispiel zeigt ziemlich gut, wie wir bei der OneSystems GmbH System Management verstehen.
Ein Monitoring-System zu installieren und einige Standardchecks einzurichten ist relativ einfach. Interessant wird es dort, wo aus den gesammelten Informationen konkrete Massnahmen entstehen.
Wir setzen Checkmk für die zentrale Überwachung von Systemen und Diensten ein und integrieren dabei auch Komponenten wie Fail2Ban. Wo Standardchecks oder vorhandene Filter nicht ausreichen, entwickeln wir eigene Checks, Regeln und Automatisierungen passend zur jeweiligen Umgebung.
Damit können neben klassischen Werten wie CPU, RAM, Storage und Erreichbarkeit auch anwendungsspezifische Zustände und Sicherheitsmechanismen überwacht werden.
Das Ziel ist dabei nicht, möglichst viele Werkzeuge auf einem Server zu installieren. Entscheidend ist, dass die vorhandenen Komponenten sinnvoll zusammenspielen, Auffälligkeiten früh sichtbar werden und sich wiederkehrende Reaktionen automatisieren lassen.
Beim Forgejo-Server bedeutete das konkret: hohe Last erkennen, Ursache analysieren, reale Client-Adressen sichtbar machen, problematische Requests früh stoppen, auffällige Systeme automatisiert sperren und anschliessend überprüfen, ob die Massnahmen tatsächlich wirken.
In diesem Fall lautete das Ergebnis: eine wieder niedrige Systemlast, ein wesentlich ruhigeres Access-Log und zeitweise mehr als 600’000 automatisch gesperrte IP-Adressen.





