2 Punkte von GN⁺ 2026-04-30 | 2 Kommentare | Auf WhatsApp teilen
  • Als Proxy-Protokoll, das Anfragen per Socket an langlaufende Backends weiterreicht, lässt es sich einsetzen, ohne die bestehende HTTP-Handler-Struktur wesentlich zu verändern
  • HTTP/1.1-Reverse-Proxying führt leicht zu abweichenden Interpretationen von Nachrichtenbegrenzungen zwischen Implementierungen und kann dadurch weiterhin schwere Sicherheitsprobleme wie Desync und Request Smuggling verursachen
  • FastCGI bietet seit 1996 eine klare Nachrichten-Framierung und trennt Client-Header strukturell von den vom Proxy hinzugefügten vertrauenswürdigen Informationen
  • Gos net/http/fcgi füllt REMOTE_ADDR in Request.RemoteAddr ein und spiegelt den HTTPS-Status auch in Request.TLS wider, sodass die Weitergabe von Vertrauensinformationen ohne zusätzliche Middleware verarbeitet werden kann
  • Es gibt Einschränkungen wie fehlende WebSocket-Unterstützung, ein schwaches Tooling-Ökosystem und bei manchen Workloads geringeren Durchsatz, doch wenn keine WebSockets nötig sind und die Leistung ausreicht, bleibt es eine praktische Option

Stellung und Einsatzweise von FastCGI

  • FastCGI wird nicht nur für das Modell verwendet, bei dem pro Datei ein Prozess gestartet wird, sondern kann auch als Proxy-Backend-Protokoll dienen, das Anfragen über TCP- oder UNIX-Sockets an langlaufende Daemons sendet
  • In Go genügt es, das Paket net/http/fcgi zu importieren und http.Serve durch fcgi.Serve zu ersetzen
    • Bestehende Handler verwenden weiterhin http.ResponseWriter und http.Request
    • Auch die übrige Struktur der Anwendung bleibt unverändert
  • Große Proxies wie Apache, Caddy, nginx und HAProxy unterstützen FastCGI-Backends, und die Konfiguration ist vergleichsweise einfach

Parsing-Probleme bei HTTP als Backend-Protokoll

  • HTTP-Reverse-Proxying kommt einem Sicherheitsminenfeld nahe, und Probleme wie die Desync-Schwachstelle im Medien-Proxy von Discord, durch die sich private Anhänge ausspähen ließen, tauchen weiter auf
  • HTTP/1.1 wirkt oberflächlich wie ein simples textbasiertes Protokoll, erlaubt aber zu viele verschiedene Darstellungen derselben Nachricht und zu viele Sonderfälle, sodass Implementierungen sie leicht unterschiedlich interpretieren
  • Das größte Problem ist, dass HTTP-Nachrichten keine explizite Framierung haben
    • Das Ende einer Nachricht wird von der Nachricht selbst auf mehrere Arten beschrieben
    • Verschiedene Implementierungen können unterschiedlich auslegen, wo eine Nachricht endet und die nächste beginnt
  • Solche Abweichungen bilden die Grundlage für HTTP-Desync-Angriffe beziehungsweise Request Smuggling und führen zu schweren Sicherheitsproblemen, weil Reverse Proxy und Backend die Nachrichtengrenzen unterschiedlich verstehen
  • Parser-Unterschiede immer weiter zu patchen, ist kaum eine grundlegende Lösung
    • James Kettle entdeckt fortlaufend neue Varianten
    • Nachdem er im vergangenen Jahr weitere Fälle gefunden hatte, verwendete er sogar die Formulierung "HTTP/1.1 must die"

Umgang mit Nachrichtengrenzen in FastCGI und HTTP/2

  • HTTP/2 kann Desync-Probleme lösen, wenn es zwischen Proxy und Backend konsistent eingesetzt wird, weil Nachrichtengrenzen eindeutig sind
  • FastCGI bietet diese klare Trennung der Grenzen bereits seit 1996 in einem einfacheren Protokoll
  • nginx unterstützt FastCGI-Backends seit der ersten Veröffentlichung, aber HTTP/2-Backends wurden erst Ende 2025 hinzugefügt
  • Die HTTP/2-Backend-Unterstützung von Apache befindet sich weiterhin im Status "experimental"

Problem mit nicht vertrauenswürdigen Headern und FastCGIs Trennungsmodell

  • Nicht nur Desync ist ein Problem: HTTP bietet auch keine robuste Möglichkeit, Daten zu transportieren, die ein Proxy vertrauenswürdig weitergeben muss, etwa die tatsächliche Client-IP, den vom Proxy verarbeiteten authentifizierten Benutzernamen oder Informationen zum Client-Zertifikat bei mTLS
  • In der Praxis landen solche Informationen in HTTP-Headern, aber zwischen den vom Proxy hinzugefügten Vertrauensdaten und den vom Client gesendeten nicht vertrauenswürdigen Headern gibt es keine strukturelle Trennung
  • Header wie X-Real-IP werden oft zur Weitergabe der echten Client-IP genutzt, sind aber nur dann sicher, wenn der Proxy alle vorhandenen entsprechenden Header vollständig entfernt und dann neu setzt, einschließlich Varianten der Groß- und Kleinschreibung
  • Dieser Ansatz ist äußerst riskant, und es gibt viele Wege, auf denen das Backend Daten vertrauen kann, die ein Angreifer eingeschleust hat
  • Der Proxy muss nicht nur X-Real-IP, sondern jeden Header für diesen Zweck löschen
  • So prüft beispielsweise die Chi-Middleware zur Ermittlung der tatsächlichen Client-IP zuerst True-Client-IP und verwendet nur dann X-Real-IP, wenn ersterer fehlt
    • Selbst wenn der Proxy X-Real-IP korrekt verarbeitet, kann ein Angreifer Probleme verursachen, indem er True-Client-IP mitsendet
  • FastCGI trennt Client-Header und vom Proxy hinzugefügte Informationen per Domain Separation
    • Beide werden zwar als Listen von Schlüssel/Wert-Parametern übertragen, aber HTTP-Headernamen erhalten das Präfix HTTP_
    • Dadurch kann ein vom Client gesendeter Header nicht strukturell als Vertrauensdatum des Proxys interpretiert werden

Verarbeitung von Vertrauensinformationen mit FastCGI in Go

  • FastCGI definiert Standardparameter wie REMOTE_ADDR, um die echte Client-IP zu übermitteln
  • Gos net/http/fcgi übernimmt diesen Wert automatisch in http.Request.RemoteAddr, sodass keine zusätzliche Middleware nötig ist
  • Der Proxy kann auch Informationen wie die Nutzung von HTTPS, die ausgehandelte TLS Cipher Suite oder das Client-Zertifikat über nicht standardisierte Parameter weitergeben
  • In Go setzt das System bei HTTPS-Anfragen automatisch das Feld TLS in Request auf einen nicht-nil-Wert
    • Selbst wenn es leer ist, ist das nützlich, um zu prüfen, ob HTTPS erzwungen wurde
  • Über fcgi.ProcessEnv lässt sich auf den vollständigen Satz der vom Proxy gesendeten Vertrauensparameter zugreifen

Warum die Verbreitung stockt und welche praktischen Grenzen es gibt

  • Wenn FastCGI besser ist, warum wird es dann nicht breiter eingesetzt? Hier scheinen das altmodisch klingende Name und ein mangelndes Bewusstsein für die Sicherheitsprobleme von HTTP-Reverse-Proxies zusammenzuwirken
  • Watchfire behandelte Desync-Angriffe bereits 2005 und warnte auch davor, dass sie schwer zu beheben seien, doch über mehr als ein Jahrzehnt hinweg erhielten solche Angriffe kaum angemessene Aufmerksamkeit
  • FastCGI ist auch heute noch produktiv einsetzbar, und bei SSLMate wird es seit über 10 Jahren in Produktion verwendet
  • Dennoch hat diese alte Technik auch Schwächen
    • Sie wurde nicht für WebSocket-Unterstützung weiterentwickelt
    • Das Tooling-Ökosystem ist schwach
    • So unterstützt curl zwar FTP, Gopher und SMTP, kann aber keine FastCGI-Anfragen senden
  • Bei Benchmarks eines Go-FastCGI-Servers hinter mehreren Reverse Proxies zeigten einige Workloads geringeren Durchsatz als mit HTTP/1.1 oder HTTP/2
    • Das wird eher als Folge davon gesehen, dass der FastCGI-Codepfad nicht so stark optimiert ist wie HTTP, und nicht als grundsätzliche Grenze des Protokolls

Schlussfolgerung

  • Wenn keine WebSockets benötigt werden und die aktuelle Leistung ausreicht, ist FastCGI weiterhin eine brauchbare Option
  • Selbst wenn ein Flaschenhals entsteht, erscheint es sinnvoller, zusätzliche Hardware einzusetzen, als die Komplexität und den Sicherheitsalbtraum von HTTP-Reverse-Proxying in Kauf zu nehmen

2 Kommentare

 
rtyu1120 2026-04-30

Der FastCGI-Kommentar von Twisted, den ich in den Lobsters-Kommentaren gefunden habe, ist wirklich beeindruckend: https://web.archive.org/web/20160723091923/…

 
GN⁺ 2026-04-30
Hacker-News-Kommentare
  • Ich stimme der Stoßrichtung des Artikels zu. Für diesen Einsatzzweck halte ich FastCGI für besser als HTTP
    Ich möchte auch das Protokoll WAS (Web Application Socket) bekannt machen. Ich habe es vor 16 Jahren bei der Arbeit selbst entworfen, weil ich fand, dass selbst FastCGI nicht gut genug war
    Statt Main-Socket-Framing verwendet es einen Control-Socket und zwei Pipes für rohe Request-/Response-Bodies, und sowohl WAS-Apps als auch Webserver können auf den Pipes splice() nutzen
    Es braucht kein Framing, Request-Abbruch ist möglich, und die drei File Descriptors lassen sich jederzeit wiederherstellen
    Ich nutze es seit Jahren für interne Anwendungen und in Webhosting-Umgebungen und habe auch selbst eine PHP SAPI geschrieben. Ziemlich viele Websites laufen intern auf WAS
    Alles ist Open Source
    library: https://github.com/CM4all/libwas
    documentation: https://libwas.readthedocs.io/en/latest/
    non-blocking library: https://github.com/CM4all/libcommon/tree/master/src/was/asyn...
    our web server: https://github.com/CM4all/beng-proxy
    WebDAV: https://github.com/CM4all/davos
    PHP fork with WAS SAPI: https://github.com/CM4all/php-src

    • FastCGI und HTTP sind nicht auf derselben Ebene
      HTTP dient der Übertragung von Daten zwischen Endpunkten wie Browser und Server, FastCGI dagegen der Verarbeitung dieser Daten zwischen Server und Anwendung
      Ich habe den Artikel gerade überflogen, und der Autor scheint verwirrend so zu schreiben, als seien beide gegenseitig austauschbar. In Wirklichkeit sind sie das überhaupt nicht
      Zur Einordnung: Ich selbst nutze fcgi seit 10 Jahren für Web-Kundendienste
  • Dieser Artikel ist gerade deshalb interessant, weil so viel fehlt
    Ich habe damals während der Hochphase der Debatte FastCGI vs. SCGI vs. HTTP ein Web2.0-Startup gegründet und den Frontend-Stack selbst aufgebaut; am Ende hat HTTP aus Gründen der Einfachheit gewonnen
    Wenn man einfach das HTTP weiterverwendet, das am Gateway ohnehin verarbeitet werden muss, braucht man kein weiteres Protokoll im Stack, und dadurch wurde es sehr einfach, mehrere Reverse-Proxy-Stufen einzuziehen oder Querschnittsthemen wie Authentifizierung, Sessions, SSL-Terminierung und DDoS-Filterung auf Server mit jeweils eigener Rolle aufzuteilen
    In der Entwicklungsumgebung konnte man direkt per HTTP an den App-Server gehen, und in Produktion konnte derselbe App-Server unverändert weiterverwendet werden, während Reverse Proxies SSL, Authentifizierung und Missbrauchserkennung übernahmen
    Damals war auch wichtig, dass nginx deutlich schneller und stabiler war als die meisten FastCGI-/SCGI-Module. Wir hatten anfangs HTTP -> Lighttpd -> FastCGI -> Django, aber einfach nginx zu verwenden war viel schneller
    Die Verwendung von HTTP funktionierte wie eine Web-Variante des End-to-End Principle. Die Idee ist, dass Netzwerke und Protokolle vom transportierten Inhalt unabhängig sein sollten und dass die Anwendungslogik an den Endpunkten sitzen sollte, nicht in Netzknoten, die filtern oder umleiten
    Der zentrale Punkt des Artikels ist allerdings, dass es aus Sicherheitssicht oft besser ist, dem Prinzip der geringsten Rechte zu folgen. Man sollte nur die erwartete Kommunikation per Allowlist zulassen, damit man nicht versehentlich zu einer Kompromittierung an anderer Stelle beiträgt
    Am Ende gibt es zwischen beiden einen Spannungsbogen. E2E bringt Flexibilität, aber diese Flexibilität schafft auch mehr Spielraum für Missbrauch; PoLP bringt Sicherheit, erlaubt aber nur das, was man entworfen hat, und erschwert dadurch die Anpassung an neue Anforderungen
    [1] https://en.wikipedia.org/wiki/End-to-end_principle
    [2] https://en.wikipedia.org/wiki/Principle_of_least_privilege

    • Ich halte diese Analogie nicht für passend, besonders nicht im Kontext von Connection Caching und Multiplexing
      Wenn ein zwischengeschaltetes Gateway mehrere HTTP-Requests in einen anderen einzelnen HTTP-Kanal multiplexed, dieser Kanal direkt bis zum Listening Service durchläuft und nicht vor dem Application Socket wieder demultiplexed wird, dann verletzt das die End-to-End-Logik auf fundamentale Weise in mehrfacher Hinsicht
      Die Analogie trägt höchstens dann einigermaßen, wenn eine 1:1-Verbindungssymmetrie erhalten bleibt
      Ich würde sagen, dass Reverse-Proxy-Schwachstellen allesamt direkt daraus entstehen, dass End-to-End verletzt wurde
      Wenn die Analogie stimmen würde, müsste auch SMTP-Übertragung über mehrere MX hinweg End-to-End sein, ist sie aber nicht, und dort treten viele ähnliche Probleme wie bei Reverse Proxies auf, etwa Message-Boundary-Desync
      Ich verstehe die Absicht, HTTP-Requests Nachrichten zuzuordnen, aber an den realen TCP-/HTTP-Semantiken und all den Protokolldetails bricht das sehr schnell auseinander
      Das End-to-End-Prinzip erlaubt keinen schlampigen Umgang mit Semantik. Es verlangt sehr strikte Disziplin bei Zustandsverwaltung und Grenzen der Transportebene. Irgendwie end-to-end-artig ist nicht End-to-End
    • Für Web-App-Entwickler ist HTTP-Semantik nützlich, aber das HTTP-Wire-Protocol selbst ist miserabel
      Multiplexing gab es zum Beispiel erst mit HTTP 2.0, daher ist es verschwenderisch, HTTP unverändert für die Kommunikation zwischen Reverse Proxy und Backend zu verwenden
      Es gibt auch Sicherheitsprobleme. Unterschiedliche Parser können sogar unterschiedlich interpretieren, wo Request-Grenzen enden
      Google verwendet schon seit Langem zwischen Front-Webserver und Anwendung nicht direkt HTTP, sondern kapselt es in das eigene Stubby-Protokoll
      Das ist viel schneller und funktionsreicher als das HTTP-Wire-Protocol. Für die meisten Unternehmen wäre das übertrieben, aber ab einer gewissen Größenordnung lohnt es sich völlig, die Kosten für ein eigenes Wire Protocol samt zugehörigem Tooling zu tragen
    • Das End-to-End Principle innerhalb eines Rechenzentrums anzuwenden, ergibt kaum Sinn und erlaubt, wie der Artikel zeigt, eher unsicheres Verhalten
    • Was ich an nginx nicht mag, ist die Dokumentation. Ich finde sie praktisch kaum brauchbar
      Auch httpd ist irgendwann in eine Richtung gegangen, die Konfiguration schwieriger macht, und ich habe es an dem Punkt aufgegeben, als das Konfigurationsformat plötzlich geändert wurde
      Ich hätte mich wohl anpassen können, bin stattdessen aber zu lighttpd gewechselt, und später hat Ruby die Erzeugung der Konfiguration automatisiert, sodass ich technisch gesehen wieder zu httpd zurückkönnte
      Trotzdem möchte ich nicht zurück. Webserver-Entwickler sollten sehr vorsichtig damit sein, Nutzer in ein neues Format zu zwingen
      Wenn man schon aus einer wirklich simplen Entscheidung heraus das Konfigurationsformat ändert, dann sollte man zumindest etwas wie YAML-Konfiguration als zusätzliche Option anbieten, statt plötzlich eine neue if-clause-artige Konfigurationssyntax aufzuzwingen
  • Jetzt, da WHATWG streams im Browser weit verbreitet sind, ist es ziemlich einfach geworden, etwas WebSocket-Ähnliches auf langlebigen HTTP-Requests selbst zu implementieren
    Man sendet einfach einen Byte-Stream und setzt vor jede Nachricht einen Header; in vielen Fällen reicht ein einzelner Längenwert
    Das hat auch Vorteile. Anders als bei WebSocket braucht man in der Server-Schicht keinen separaten Sonderpfad, man kann Backpressure nutzen, bekommt Verbesserungen aus HTTP/2 und HTTP/3 gratis mit, und der Framing-Overhead ist geringer
    Soweit ich weiß, wird es allerdings noch nicht unterstützt, den Request-Body weiter zu streamen und gleichzeitig eine Response zu empfangen, daher braucht man für vollständiges bidirektionales Streaming zwei Requests

  • Ich habe das alte plain CGI wiederentdeckt, und für unsere Plattform ist es hervorragend geeignet, damit Nutzer eigene Seiten per Vibe Coding erstellen können [1]
    Als Grundfunktionen bieten wir eine Task List und einen Data Viewer, aber Nutzer wollen oft viel feinere Anpassungen, etwa eine Kanban-Ansicht oder ein eigenes Dashboard mit Datenfiltern und Charts
    In dieser Box gibt es einen Coding Agent, also können Nutzer statt eines traditionellen Report Builders direkt selbst den gewünschten Code erstellen
    Die Go-Stdlib unterstützt das sowohl serverseitig als auch im User Space gut, und wenn der Coding Agent page-name/main.go erstellt und per CGI kommunizieren lässt, delegiert der Server Requests dorthin
    Da Datenvolumen und Pageviews alle im Person-Scale-Bereich liegen, brauchen wir Optimierungen wie FastCGI gar nicht wirklich
    Im Zeitalter der Agents wird alte Technologie wieder neu

    1. https://housecat.com
    • Man sollte beachten, dass CGI im Unterschied zu FastCGI HTTP-Header über Umgebungsvariablen übergibt und dadurch einen ziemlich großen Fallstrick hat: https://httpoxy.org/
      Gos CGI-Serverimplementierung setzt $HTTP_PROXY nicht und ist in diesem Punkt daher sicher, aber mir gefällt trotzdem nicht, wie CGI generell Umgebungsvariablen verwendet
  • Auf der Reverse-Proxy-Seite waren die Aufgaben meist einfach genug, dass die eingebauten Funktionen von Nginx ausgereicht haben
    Trotzdem wäre mir die Idee, dafür FastCGI zu verwenden, wohl nie gekommen
    Vor etwa 10 Jahren habe ich FastCGI kurz genutzt, um etwas C++-Code über das Web laufen zu lassen, aber seitdem praktisch nicht mehr

    • Heute sind Embedded Server viel verbreiteter
      Man packt einfach direkt einen HTTP-Server in die Anwendung und erledigt dort, was nötig ist, ganz ohne Gateway
  • Die mit Red Hat verwandten PHP/Apache-Setups verwenden FPM (FastCGI Process Manager)
    Ich weiß nicht, ob FastCGI in RHEL-Distributionen noch an anderer Stelle verwendet wird
    $ rpm -qi php-fpm | grep ^Summary
    Summary : PHP FastCGI Process Manager

  • Es gibt auch das uwsgi protocol
    Im Grunde hat auch das eher den Charakter eines RPC für fast alles

  • FCGI ist auch ein Orchestrierungssystem
    Wenn die Last steigt, startet es mehr Server-Tasks, bei sinkender Last fährt es sie wieder herunter, und wenn ein Task abstürzt, startet es eine neue Kopie
    Eine Art Kubernetes für ein einzelnes System

    • Meiner Erfahrung nach war diese Funktion nicht besonders gut
      Das klingt gut, aber oft lief es bei normaler geringer Last problemlos und bei hoher Last wurde dann durch zusätzliche Worker der Speicher leergezogen
      Daher war eine statische Worker-Zahl in der Regel besser
      Crash Recovery kann aber nützlich sein, wenn man sie braucht
    • Genau so haben wir es auch genutzt
  • Es lohnt sich, kurz die Absurdität von HTTP-Headern zu würdigen
    Wenn True-Client-IP fehlt und man nur dann X-Real-IP verwendet, kann ein Angreifer einen True-Client-IP-Header schicken und damit Erfolg haben, selbst wenn der Proxy X-Real-IP korrekt setzt
    Es gibt X-Forwarded-For, X-Real-IP und je nach CDN ganz unterschiedliche Custom Header, manche davon sind kommagetrennte Listen, oft sogar noch nutzloserweise ergänzt um die IP unseres eigenen LB
    Ich verstehe, warum das so ist, aber hilfreich ist es überhaupt nicht
    Außerdem können all diese Header von einem bösartigen User-Agent eingeschleust werden. Es wirkt, als hätte sich nie jemand darauf geeinigt, wie vertrauenswürdige Server in einer Pipeline wichtige Informationen weitergeben sollen
    Dieses Chaos passt auch gut zur Absurdität des User-Agent-Headers
    Dort ist es noch extremer geworden, seit Apple im Namen der Privatsphäre beschlossen hat, komplett falsche Informationen zu senden, etwa erfundene OS-Versionen

  • An dieser Behauptung ist viel Wahres dran, aber FastCGI folgt bei Dingen wie PATH_INFO CGI/1.1, wodurch Informationen verloren gehen
    URL-Decoding wird erzwungen, sodass ein encoded slash wie %2F nicht dargestellt werden kann
    Je nach Implementierung wird // im Pfad auch zu / zusammengezogen; dieses Problem gibt es allerdings auch in verschiedenen HTTP-Implementierungen
    In Sachen Ausdruckskraft ist es HTTP unterlegen, und ob dieser Unterschied wichtig ist, hängt von der Anwendung ab
    Ich bevorzuge es, URLs präzise zu behandeln