Webentwicklung

Request-IDs für Nginx und PHP - Eine Anfrage durch die Logs verfolgen

Request-IDs für Nginx und PHP - Eine Anfrage durch die Logs verfolgen

Eine PHP-Anfrage schlägt fehl, doch die sichtbaren Hinweise sind verstreut. Nginx protokolliert Status und Dauer in einer Datei. PHP erfasst eine Exception an anderer Stelle. Mehrere Anfragen können innerhalb derselben Sekunde dieselbe Route erreicht haben. Welches Anwendungsereignis gehört zu der untersuchten Zeile im Access-Log?

Eine Request-ID bietet eine bewusst kleine Antwort: Am Rand des Webservers wird eine opake Kennung erzeugt, an PHP weitergegeben und in jedem relevanten Eintrag dieser Anfrage festgehalten. Sie erklärt den Fehler nicht von selbst. Sie erleichtert es, die vorhandenen Hinweise miteinander zu verbinden.

Dieser Artikel entwickelt dieses eng begrenzte Muster für Nginx und PHP-FPM. Das Beispiel behandelt einen vom Client gelieferten Header nicht als vertrauenswürdig, hält personenbezogene Daten aus der Kennung heraus und bezeichnet eine einzelne ID nicht als „Distributed Tracing“.

Das Problem ist die Korrelation, nicht ein Mangel an Logs

Webserver- und Anwendungslogs beantworten unterschiedliche Fragen. Ein Nginx-Access-Log kann zeigen, wann eine Anfrage abgeschlossen wurde, welchen HTTP-Status sie hatte und wie lange die Verarbeitung dauerte. Der Anwendungscode weiß, welcher Vorgang fehlgeschlagen ist und warum. Laut dem OWASP Logging Cheat Sheet ergänzt Application Logging den Kontext, der in Infrastructure Logs allein häufig fehlt.

Zeit, Pfad und Client-Adresse können beim Abgleich helfen, sind aber schwache Verbindungsschlüssel. Gleichzeitig verarbeitete Anfragen können dieselben Werte besitzen. Eine Request-ID schafft eine direktere Verbindung:

Nginx access log:
request_id=91f... status=500 request_time=0.184

PHP application log:
request_id=91f... event=profile_update_failed

Die nützliche Eigenschaft ist die Konsistenz. Derselbe Wert erscheint für eine Anfrage an jedem Beobachtungspunkt. Die ID sollte eine Kennung bleiben und nicht zu einem Behälter für Benutzername, E-Mail-Adresse, IP-Adresse, Zeitstempel oder Fehlerbeschreibung werden.

Der ID eine einzige vertrauenswürdige Quelle geben

Nginx stellt die eingebaute Variable $request_id bereit. Die Dokumentation des Core-Moduls beschreibt den Wert als 16 zufällige Bytes in hexadezimaler Darstellung. Damit erhält dieser kleine Stack einen praktischen, serverseitig erzeugten Wert, bevor PHP die Anfrage verarbeitet.

Die Vertrauensgrenze ist wichtig. Ein öffentlicher Client kann einen Header namens X-Request-ID senden. Wird er unverändert akzeptiert, können wiederholte, irreführende oder fehlerhaft formatierte Werte in die Logs gelangen. Es gibt berechtigte Architekturen, in denen ein vertrauenswürdiger Reverse Proxy bereits vorhandenen Korrelations- oder Trace-Kontext weitergibt. Dafür ist eine ausdrückliche Vertrauensrichtlinie nötig. Die einfachere Ausgangsbasis hier ist eine andere: Nginx erzeugt die maßgebliche lokale ID und übergibt sie über einen eigenen FastCGI-Parameter an PHP.

Der daraus entstehende Vertrag ist kurz:

  • Nginx erzeugt den Wert.
  • Nginx schreibt ihn in das Access-Log.
  • Nginx übergibt denselben Wert an PHP-FPM.
  • Anwendungslogs nehmen ihn unverändert in relevante Einträge auf.
  • Der Wert wird niemals für Authentication, Authorization oder Idempotency verwendet.

Die Request-ID in das Nginx-Access-Log aufnehmen

Das Nginx-Log-Modul erlaubt ein benanntes log_format mit Variablen. Es unterstützt außerdem escape=json, wodurch Zeichen maskiert werden, die nicht unmaskiert in JSON-Strings vorkommen dürfen.

Ein kompaktes Format kann im http-Kontext definiert werden:

log_format correlated escape=json
    '{"time":"$time_iso8601",'
    '"request_id":"$request_id",'
    '"method":"$request_method",'
    '"uri":"$uri",'
    '"status":$status,'
    '"request_time":$request_time}';

access_log /var/log/nginx/app-access.log correlated;

Dieses Format protokolliert bewusst keinen Query-String. Query-Parameter enthalten gelegentlich Tokens, Suchbegriffe, E-Mail-Adressen oder andere Daten, die nicht in ein routinemäßiges Log gehören. Sie wegzulassen ist keine vollständige Datenschutzrichtlinie, aber eine nützliche Voreinstellung zur Datenminimierung. Auch die URI und andere Metadaten können sensibel sein, daher bleiben Zugriff und Aufbewahrung der Logs eigenständige Entscheidungen.

JSON-Escaping ist ebenfalls keine magische Sicherheitsschicht. Es schützt die Struktur dieses Ausgabeformats. OWASP empfiehlt davon unabhängig, Ereignisdaten aus anderen Vertrauenszonen zu bereinigen und Geheimnisse sowie sensible Kennungen auszuschließen. Eine Request-ID kann das Protokollieren von Passwörtern oder Access Tokens nicht ausgleichen.

Den serverseitig erzeugten Wert an PHP übergeben

Innerhalb der PHP-Location wird der Wert als ausdrücklicher FastCGI-Parameter übergeben:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param REQUEST_ID $request_id;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}

Der Socket-Pfad ist nur ein Platzhalter: Debian-Installationen enthalten häufig die PHP-Version im Dateinamen, andere Systeme verwenden möglicherweise TCP oder einen anderen Pfad. Er muss zum tatsächlich eingesetzten PHP-FPM-Pool passen. Entscheidend ist die Zeile fastcgi_param REQUEST_ID $request_id;. Die Dokumentation des FastCGI-Moduls erklärt, dass die Direktive einen Parameter übergeben kann, dessen Wert Nginx-Variablen enthält.

PHP stellt Werte des Servers und der Ausführungsumgebung gewöhnlich über $_SERVER bereit. Das PHP-Handbuch weist jedoch darauf hin, dass die verfügbaren Einträge vom Webserver abhängen. Mit der obigen Nginx-Konfiguration kann der Bootstrap-Code der Anwendung den Wert einmal erfassen:

<?php

$requestId = $_SERVER['REQUEST_ID'] ?? 'missing';

error_log(sprintf(
    '[request_id=%s] event=profile_update_failed',
    $requestId
));

Das error_log()-Handbuch erläutert, dass das Ziel von der PHP-Konfiguration und dem gewählten Message Type abhängt. Eine reale Anwendung besitzt möglicherweise bereits einen Logger mit strukturiertem Kontext. Dann sollte request_id diesem Kontext hinzugefügt werden, statt direkte error_log()-Aufrufe im gesamten Business Code zu verteilen.

Der Fallback-Wert dient der Diagnose und ist keine Ersatz-ID. missing zeigt einem Operator, dass die Anfrage nicht durch die erwartete Konfiguration gelaufen ist oder dass die Anwendung über eine andere SAPI wie die CLI gestartet wurde. Eine zweite, unabhängige ID in PHP zu erzeugen, würde diese Konfigurationslücke verbergen und die Korrelation mit Nginx unterbrechen.

Die ID zurückzugeben kann den Support unterstützen

Wenn Leser oder API-Clients eine Kennung melden sollen, kann Nginx denselben Wert in einem Response Header zurückgeben:

add_header X-Request-ID $request_id always;

Die Dokumentation des Nginx-Headers-Moduls beschreibt, dass add_header Variablen akzeptiert und always das Feld unabhängig vom Response-Status hinzufügt. Das ist nützlich, wenn gerade eine Fehlerantwort untersucht werden soll.

X-Request-ID ist in diesem Beispiel eine lokale Konvention und kein standardisierter Security Header. Die Rückgabe ist optional. Wird der Wert offengelegt, sollten Support-Werkzeuge ihn als Suchschlüssel und nicht als Beweis für die Echtheit einer Meldung behandeln. Jeder Empfänger einer Antwort kann deren ID kennen, und der Wert darf niemals Daten oder Aktionen freischalten.

Den gesamten Pfad prüfen, nicht nur die Syntax

Eine sorgfältige Einführung hat zwei Ebenen. Zuerst sollte die Nginx-Konfiguration vor einem Reload mit dem zum System passenden Befehl und Service-Verfahren validiert werden. Ein kopierter falscher Socket-Pfad oder eine Direktive im falschen Kontext sollte hier scheitern und nicht erst während eines Vorfalls.

Danach wird eine kontrollierte Anfrage an eine harmlose Test-Route gesendet und an drei Stellen verglichen:

  1. Im Response Header, falls er aktiviert ist.
  2. Im Eintrag des Nginx-Access-Logs.
  3. In einem absichtlich erzeugten Anwendungsereignis derselben Anfrage.

Alle drei sollten denselben Wert enthalten. Anschließend sollte ein sicher auslösbarer Anwendungsfehlerpfad getestet werden. Schließlich ist zu prüfen, was geschieht, wenn das Logging-Ziel nicht verfügbar oder voll ist. OWASP empfiehlt ausdrücklich Tests von Logging-Fehlern, Berechtigungen, Injection-Resistenz und Ressourcenerschöpfung. Korrelation ist nicht verlässlich, wenn eine Seite unbemerkt nicht mehr aufzeichnet.

Für diesen Test müssen weder Geheimnisse ausgegeben noch vollständige Request Bodies protokolliert werden. Ein fester Ereignisname und die Request-ID reichen für den Korrelationstest aus.

Verstehen, was dieses Muster nicht leistet

Es setzt die ID nicht automatisch überall ein

Die Konfiguration verbindet das Nginx-Access-Log mit den PHP-Ereignissen, die REQUEST_ID ausdrücklich aufnehmen. Sie fügt den Wert nicht automatisch jeder Nginx-Fehlermeldung, PHP-Warnung, Datenbankabfrage, Queue-Aufgabe oder ausgehenden HTTP-Anfrage hinzu. Jede weitere Grenze erfordert eine bewusste Kontextbehandlung.

Es ist kein Distributed Tracing

Die W3C-Trace Context Recommendation definiert traceparent und tracestate, um Trace-Beziehungen über Komponenten hinweg weiterzugeben. Ein traceparent-Wert enthält Trace-ID, Parent-ID, Flags und Version. Dieses Modell kann eine Kette von Operationen und Parent-Child-Beziehungen darstellen. Eine einzelne Nginx-Request-ID, die nach PHP kopiert wird, kann das nicht.

Für einen kleinen Monolithen kann diese Einschränkung akzeptabel sein. Wenn eine Browseraktion mehrere Services, Queues und Background Worker auffächert, ist die Einführung eines standardisierten Tracing-Systems meist schlüssiger, als X-Request-ID immer mehr Bedeutungen zu geben. Die lokale ID kann weiterhin nützlich sein, doch ihre Rolle sollte ausdrücklich begrenzt bleiben.

Es macht Logs nicht automatisch vertrauenswürdig oder harmlos

Eine Kennung hilft, Einträge zu verbinden. Sie beweist nicht, dass jeder Eintrag vollständig, unverändert oder korrekt zugeordnet ist. Logs benötigen eingeschränkten Zugriff, Rotation, Aufbewahrungsregeln, Monitoring und Schutz vor Veränderungen. Auch ihre Inhalte erfordern Disziplin. OWASP rät davon ab, Passwörter, Access Tokens, Session-IDs, Connection Strings, Encryption Keys und sensible personenbezogene Daten direkt zu protokollieren.

Hinzu kommt ein stiller Datenschutzkompromiss: Korrelation ist gerade deshalb nützlich, weil sie Ereignisse miteinander verknüpft. Die Kennung sollte opak bleiben, nicht aus Benutzerdaten abgeleitet werden, und korrelierte Datensätze sollten nur so lange aufbewahrt werden, wie ihr betrieblicher oder sicherheitsbezogener Zweck es verlangt.

Eine kleine Verbindung zwischen zwei nützlichen Perspektiven

Eine Request-ID funktioniert am besten, wenn sie langweilig bleibt. Nginx erzeugt einen opaken Wert, protokolliert ihn zusammen mit Request-Metadaten und gibt ihn an PHP weiter. Die Anwendung ergänzt den Wert bei aussagekräftigen Ereignissen. Operatoren können dann von „ungefähr zu dieser Zeit trat ein 500-Fehler auf“ zu „diese Einträge beschreiben dieselbe Anfrage“ wechseln.

Das ist eine bescheidene Verbesserung und keine Observability-Plattform. Sie ersetzt weder sorgfältiges Event Design, sicheres Logging und Monitoring noch Distributed Tracing, wenn ein System über Prozessgrenzen hinauswächst. Für eine kleine Nginx- und PHP-Anwendung kann eine vertrauenswürdige Kennung jedoch aus zwei getrennten Logs eine deutlich nützlichere Unterhaltung machen.

References