Schuld war der Anbieter, bis die Logs etwas anderes sagten

21.08.2026

Thema: alle Beiträge zu Kubernetes

Meine Automation fiel aus, die Schuldfrage schien klar: der SaaS-Anbieter. Dann drehte ein Satz vom Support die Beweislast um, und die Ursache saß in meiner eigenen WAF. Ein Postmortem übers Schuld-Zuweisen.

Inhaltsverzeichnis

Der bequeme Verdächtige

Einen Tag nach dem Umzug von trutz.io auf den neuen Server meldete sich meine Automation krank. Make, der Dienst, der neue Blog-Posts automatisch zu LinkedIn bringt, lieferte bei jedem Lauf denselben Fehler:

Couldn't connect
ECONNRESET: Service is temporarily unavailable.
Code: ConnectionError

Die Webseite selbst lief tadellos. Der Feed unter /linkedin.xml öffnete im Browser, curl von drei verschiedenen Rechnern lieferte brav HTTP 200, das DNS zeigte sauber auf die neue Adresse. Alles funktionierte, nur dieser eine Dienst nicht.

Damit war die Schuldfrage doch offensichtlich. Mein Server: nachweislich gesund. Der Anbieter: nachweislich der Einzige mit Problemen. Und ich hatte sogar eine elegante Theorie.

Eine Theorie, die alles erklärt

Der Umzug war frisch, und die alte Server-IP hatte ich am selben Tag freigegeben. Wer nach einem Umzug noch die alte Adresse ansteuert, bekommt Verbindungsfehler: Das passte. Es passte sogar doppelt, denn auf der alten IP saß inzwischen wieder eine Maschine, die eingehende Verbindungen auf Port 443 aktiv abweist, und genau so ein aktives Abweisen meldet ein Client als ECONNRESET, nicht als Timeout.

Ich baute also eine Beweiskette: DNS-Wechsel dokumentiert, TTL 300 längst abgelaufen, Fehlerbild passt zur alten IP. Diagnose: Der Anbieter cached DNS weit über die TTL hinaus, sein Problem. Ich schrieb ein Ticket, legte die Messungen bei und lehnte mich zurück. Die Geschichte war rund.

Rund ist nicht dasselbe wie richtig.

Der Satz, der die Beweislast umdreht

Die Antwort vom Support war freundlich, nahm meine Theorie ernst und enthielt dann diesen einen Satz:

Please check whether the request reached your new server, and whether any connections from our outbound IP addresses were blocked or reset.

Übersetzt: Schau in deine eigenen Logs, bevor du auf unsere zeigst. Der Support lieferte sogar die Liste der Absender-Adressen mit, gegen die ich prüfen sollte.

Also gut. Envoy-Access-Logs des Gateways, 24 Stunden, gefiltert auf die sechs IPs des Anbieters. Wenn meine Theorie stimmte, durfte da: nichts stehen.

Da standen 21 Einträge.

{
  "user-agent": "Make/production",
  "x-envoy-origin-path": "/linkedin.xml",
  "response_code": 200,
  "bytes_sent": 0,
  "response_code_details": "response_payload_too_large"
}

Die Requests kamen an. Alle. Seit dem Umzug hatte der Anbieter meinen neuen Server einwandfrei erreicht, 18 von 18 Abrufen des Feeds, und jeder einzelne endete gleich: Status 200, aber 0 gesendete Bytes, quittiert mit response_payload_too_large. Der Server sagte „alles okay”, schickte die Kopfzeilen los und brach dann mitten in der Antwort ab. Für den Client auf der anderen Seite sieht so ein Abbruch aus wie: ECONNRESET.

Meine DNS-Theorie war in diesem Moment tot. Der Fehler saß in meiner eigenen Infrastruktur.

Warum es nur die anderen traf

Der Reihe nach. Vor meiner Webseite läuft eine WAF, OWASP Coraza im Envoy Gateway. Sie prüft nicht nur eingehende Requests, sie inspizierte bis zu diesem Tag auch ausgehende Antworten, und zwar genau die textartigen: HTML, XML. Bilder und PDFs streamen ungeprüft vorbei.

Um eine Antwort zu inspizieren, muss man sie erst vollständig einsammeln. Gepuffert wird im Stream-Puffer von Envoy, und der hat zwei verschiedene Größen: 32 KiB bei HTTP/1.1, 64 KiB bei HTTP/2. Mein Feed ist 48 KB groß. Er passt in den einen Puffer und sprengt den anderen.

Damit war der Fehler perfekt getarnt:

Jeder Test, den ich von Hand machte, lief über HTTP/2 und bestätigte mir: Der Server ist gesund. Der Bug traf ausschließlich die Clients, mit denen niemand von Hand testet. Es gibt kaum eine gemeinere Sorte.

Die Gegenprobe, einmal gefunden, ist beschämend einfach:

curl --http1.1 https://trutz.io/linkedin.xml
# curl: (18) end of response with 48114 bytes missing

Ein Flag. Dieses eine Flag hätte mir Theorie, Ticket und Schuldzuweisung erspart.

Der Nebenbefund, der wehtut

Mit der Ursache in der Hand stellte sich die unangenehme Anschlussfrage: Was ist noch größer als 32 KiB und textartig? Antwort: meine beiden längsten Blog-Artikel. Beide lieferten über HTTP/1.1 exakt dasselbe Bild, 200er-Header und dann Abbruch. Seit Tagen, für jeden HTTP/1.1-Client, also auch für so manchen Crawler und Feed-Reader. Gemerkt hatte ich: nichts.

Wer auf andere zeigt, prüft die anderen. Wer die Ursache sucht, findet auch die eigenen Schäden.

Der Fix und die Abwägung

Die Lösung war am Ende eine Zeile in der WAF-Konfiguration:

SecResponseBodyAccess Off

Keine Response-Inspektion mehr. Das ist kein fauler Kompromiss, sondern eine Abwägung: Auf einer statischen Seite prüft die Response-Inspektion meinen eigenen, aus Git gebauten Content auf Datenlecks. Der Nutzen ist nahe null, und der Versagensmodus, nämlich legitime Antworten abzubrechen, hatte gerade bewiesen, was er kostet. Die Request-Inspektion, der eigentliche Schutz vor XSS- und Injection-Mustern, bleibt vollständig scharf; der Test-Angriff kassiert weiterhin seine 403.

Was ich mir merke

  1. Jede Fehlerart hat eine Signatur. Ein 403 ist eine WAF-Regel. Ein Timeout ist eine Firewall, die still verwirft. Ein TCP Reset ist eine Maschine, die aktiv ablehnt. Wer die Signatur ernst nimmt, sucht von Anfang an am richtigen Ort, die Grundlagen dazu stehen hier.
  2. Die plausibelste Geschichte ist eine Hypothese, kein Befund. Meine DNS-Theorie war schlüssig, elegant, mit Indizien unterfüttert, und falsch. Der Unterschied zwischen Hypothese und Befund ist die Gegenprobe im eigenen Log.
  3. Beweislast zuerst bei dir. „Erreichen die Requests meinen Server?” ist die erste Frage, nicht die letzte. Sie kostet fünf Minuten und entscheidet, ob das Ticket eine Schuldzuweisung ist oder eine brauchbare Fehlermeldung.
  4. Teste wie deine Clients, nicht wie du selbst. Ein curl --http1.1 gehört neben das normale curl, sobald Automationen im Spiel sind.
  5. Der Mensch im Support hatte recht. Der eine Satz mit der Beweislast war mehr wert als meine ganze Beweiskette.

Und eine Beobachtung zum Schluss, die für mich der eigentliche Punkt ist: Ich konnte diese Ursache nur finden, weil mir die ganze Kette gehört, vom Gateway über die WAF bis zum Access-Log. Eine Log-Abfrage, eine Konfigurationszeile, gefixt. Auf einer Plattform, in die ich nicht hineinschauen kann, wäre dieselbe Suche beim Schulterzucken zweier Support-Teams geendet, die aufeinander zeigen. Verstehen, was man betreibt, ist keine Kür. Es ist der Unterschied zwischen einer Fehlersuche und einem Schwarzer-Peter-Spiel.

Dass ich Behauptungen grundsätzlich selbst nachmesse statt sie zu glauben, habe ich schon bei den Docker-Hub-Rate-Limits gepredigt. Diesmal hat mich die Gewohnheit gerettet, nur eben eine Theorie zu spät: Gemessen habe ich sofort, an der eigenen Hypothese gezweifelt erst auf Aufforderung.

© 2026 Trutz Software Consulting GmbH. CC BY 4.0: Dieser Text darf mit Namensnennung weiterverwendet und bearbeitet werden, auch kommerziell. Code-Beispiele darin stehen unter Apache 2.0. Ausgenommen sind Marke und Logo sowie gekennzeichnete Zitate Dritter. Was das genau heißt.

Newsletter

Erhalte praxisnahe Tutorials, Best Practices und echte Lösungen für die häufigsten DevOps-Probleme direkt in Dein Postfach.

Datenschutz: Deine E-Mail-Adresse wird DSGVO-konform in Deutschland gespeichert. Keine Spam, nur relevante Inhalte. Eine E-Mail alle 1 bis 2 Wochen.