NGINX vs. Caddy: Ein ehrlicher Stabilitätsvergleich für Ghost und ActivityPub

NGINX vs. Caddy: Ein ehrlicher Stabilitätsvergleich für Ghost und ActivityPub
Photo by Markus Winkler / Unsplash

Wer seinen Ghost-Blog mit ActivityPub-Unterstützung betreibt, steht früher oder später vor einer Grundsatzentscheidung: Welchen Reverse Proxy setze ich ein? Caddy gilt als modern, entwicklerfreundlich und wirbt mit automatischem HTTPS – klingt verlockend. NGINX hingegen ist alt, kampferprobt und manchmal sperrig in der Konfiguration. Doch was passiert, wenn man beide im echten Betrieb vergleicht – speziell im Zusammenhang mit dem ActivityPub-Protokoll und Ghost?

Dieser Artikel ist kein Marketing-Vergleich auf Basis von Benchmark-Tabellen. Es ist ein Erfahrungsbericht aus dem produktiven Betrieb, ergänzt um technische Hintergründe, die erklären, warum sich beide Proxys bei ActivityPub so unterschiedlich verhalten.


Kurz zur Ausgangslage: Ghost und ActivityPub

Ghost unterstützt seit Version 5.x das ActivityPub-Protokoll – das dezentrale Kommunikationsprotokoll hinter Mastodon, Lemmy und dem sogenannten Fediverse. Über ActivityPub können Ghost-Blogs mit Mastodon-Instanzen interagieren: Artikel werden als Notizen verteilt, Antworten kommen zurück, Follows werden synchronisiert.

Das klingt simpel, ist es aber nicht. ActivityPub ist ein HTTP-basiertes Protokoll mit sehr spezifischen Anforderungen:

  • Signierte HTTP-Requests (HTTP Signatures): Jede Aktivität wird vom sendenden Server signiert. Der empfangende Server validiert diese Signatur gegen den öffentlichen Schlüssel, der im /.well-known/webfinger- und ActivityPub-Actor-Endpoint liegt.
  • Strikte Header-Anforderungen: Content-Type: application/activity+json muss korrekt weitergeleitet werden. Manipulationen oder fehlende Header führen zu stillschweigenden Fehlern.
  • Zeitkritische Verarbeitung: HTTP-Signaturen haben ein kurzes Ablaufzeitfenster (oft 60 Sekunden). Verzögerungen, Queues oder Caching können dazu führen, dass Signaturen beim Empfänger bereits abgelaufen sind.
  • WebFinger-Endpoint: /.well-known/webfinger muss korrekt geroutet werden – fehlerhafte Weiterleitungen oder Header-Manipulationen brechen die Actor-Discovery.

Wer also einen Reverse Proxy vor Ghost schaltet, der auch nur in einem dieser Punkte unzuverlässig agiert, wird früher oder später Probleme mit der Föderation erleben.


NGINX: Der verlässliche Veteran

NGINX existiert seit 2004. Es ist stabiler Bestandteil von Produktionsumgebungen weltweit – von kleinen Blogs bis zu Netflix. Die Konfiguration ist deklarativ, gut dokumentiert und seit Jahren unverändert zuverlässig.

Was NGINX richtig macht

Transparente Header-Weiterleitung: In einer typischen Ghost-Konfiguration mit NGINX werden alle Header explizit durchgereicht:

location / {
    proxy_pass http://localhost:2368;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Was man nicht konfiguriert, wird nicht verändert. NGINX ist in diesem Sinne ein ehrlicher Durchleiter: kein heimliches Caching, keine automatischen Header-Transformationen, die man nicht explizit angeordnet hat.

Stabiles Prozessmodell: NGINX verwendet einen Master-Prozess mit Worker-Prozessen. Konfigurationsänderungen werden mit nginx -s reload graceful neu geladen – laufende Verbindungen bleiben bestehen. Es gibt keinen automatischen Neustart im Hintergrund, keine automatischen Zertifikatserneuerungen, die den Proxy kurzzeitig in einen undefinierten Zustand bringen könnten.

Vorhersehbares TLS-Verhalten: Zusammen mit Certbot (Let's Encrypt) ist TLS in NGINX ein gelöstes Problem. Zertifikatserneuerung läuft per Cron oder systemd-Timer, ist entkoppelt vom Proxy-Betrieb und produziert keine unerwarteten Nebeneffekte.

Kein automatisches State-Management: NGINX speichert keinen internen Zustand über den laufenden Betrieb hinaus. Es gibt keine Datenbank, keinen eingebetteten ACME-Client, der im Hintergrund Zertifikate verwaltet und dabei möglicherweise Verbindungen unterbricht.


Caddy: Modern, aber komplex unter der Haube

Caddy ist seit Version 2 ein leistungsfähiger Reverse Proxy mit eingebautem ACME-Client (automatisches HTTPS über Let's Encrypt oder ZeroSSL), einer API-Schnittstelle für dynamische Konfiguration und einer deutlich benutzerfreundlicheren Konfigurationssprache (Caddyfile).

Was Caddy gut macht

  • Automatisches HTTPS ohne externe Tools – ideal für einfache Setups
  • Übersichtliche Konfigurationssyntax
  • Eingebaute HTTP/2- und HTTP/3-Unterstützung
  • Dynamische Konfiguration via Admin-API ohne Neustart

Wo Caddy bei ActivityPub ins Straucheln gerät

Hier wird es interessant – und für Ghost-Betreiber relevant.

1. Automatische Header-Modifikation

Caddy kann unter bestimmten Umständen Header automatisch modifizieren oder ergänzen. Insbesondere der Content-Type-Header ist ein bekanntes Problemfeld: Wenn Caddy entscheidet, Responses zu puffern oder zu verändern, kann der application/activity+json-Content-Type verloren gehen oder durch einen generischen application/json ersetzt werden. Empfangende ActivityPub-Instanzen interpretieren dann die Aktivität möglicherweise falsch oder lehnen sie stillschweigend ab.

2. Zertifikatsmanagement und transiente Ausfälle

Caddys eingebauter ACME-Client ist komfortabel, aber er läuft innerhalb des Proxy-Prozesses. Bei der Zertifikatserneuerung – die automatisch und ohne manuelle Intervention erfolgt – gibt es kurze Zeitfenster, in denen Caddy intern in einem Übergangszustand ist. In dieser Phase können eingehende Requests verzögert oder mit unerwarteten Antworten beantwortet werden.

Für normale Web-Requests ist das unproblematisch: Ein Browser wartet kurz, lädt neu. ActivityPub-Anfragen von Mastodon-Instanzen hingegen haben strikte Timeouts. Wenn ein eingehender Activity-POST zu lange wartet oder eine fehlerhafte Antwort erhält, wird er nicht wiederholt – die Aktivität geht verloren.

3. Das Caching-Problem

Caddy kann bei bestimmten Konfigurationen (insbesondere in Kombination mit Plugins) aggressiv cachen. Der /.well-known/webfinger-Endpoint darf jedoch nicht gecacht werden: Wenn ein Mastodon-Server den Actor-Schlüssel eines Ghost-Blogs abruft und eine gecachte, veraltete Version erhält, schlägt die Signaturvalidierung fehl. Die Folge ist keine Fehlermeldung – die Aktivitäten werden einfach nicht mehr verarbeitet.

4. Intermittierende Föderationsprobleme: das eigentliche Symptom

Das typische Fehlerbild sieht so aus: Ghost föderiert problemlos über Wochen. Dann – ohne erkennbaren Auslöser – stoppt die Föderation. Neue Posts erscheinen nicht mehr auf verbundenen Mastodon-Instanzen, Follows werden nicht synchronisiert. Ein Neustart von Caddy behebt das Problem vorübergehend. Nach einiger Zeit tritt es erneut auf.

Was passiert im Hintergrund? Wahrscheinlich ein Zusammenspiel mehrerer Faktoren:

  • Caddy hat intern seinen Zustand (Zertifikat, Routing-Konfiguration, HTTP/2-Sessions) neu aufgebaut
  • Dabei wurden aktive Verbindungen oder Queues unterbrochen
  • Ghost hat seinerseits einen Retry-Mechanismus, der nach mehreren Fehlversuchen aufgibt
  • Das Fediverse auf der Gegenseite markiert den Server als temporär nicht erreichbar und versucht es nicht mehr

Bis man das bemerkt, sind Tage vergangen.


Der direkte Vergleich

Kriterium NGINX Caddy
Stabilität im Dauerbetrieb ✅ Sehr hoch ⚠️ Gut, aber mit gelegentlichen Transients
Header-Transparenz ✅ Vollständig kontrollierbar ⚠️ Teilweise automatische Modifikationen
ActivityPub-Kompatibilität ✅ Zuverlässig ⚠️ Abhängig von Konfiguration
TLS-Management ⚠️ Extern via Certbot ✅ Eingebaut und automatisch
Konfigurationskomplexität ⚠️ Höher, aber vorhersehbar ✅ Einfacher
Debugging ✅ Ausführliche Logs, bekannte Toolchain ⚠️ Interner Zustand schwerer einsehbar
Graceful Reload ✅ Ja ✅ Ja
HTTP/3 Support ⚠️ Experimentell (nginx 1.25+) ✅ Nativ
Vorhersehbarkeit ✅ Sehr hoch ⚠️ Mittel

Empfehlung für Ghost mit ActivityPub

Wenn du Ghost mit Fediverse-Unterstützung betreibst und Wert auf zuverlässige, dauerhafte Föderation legst: Verwende NGINX.

Nicht weil Caddy ein schlechter Proxy wäre. Caddy ist für viele Anwendungsfälle hervorragend geeignet. Aber ActivityPub ist ein Protokoll, das auf strikter HTTP-Konformität, zuverlässiger Header-Weiterleitung und stabilen Verbindungen basiert. NGINX liefert genau das – ohne Überraschungen, ohne internen Zustand, der im Hintergrund kippt.

Minimale NGINX-Konfiguration für Ghost mit ActivityPub

server {
    listen 443 ssl http2;
    server_name deinblog.de;

    ssl_certificate /etc/letsencrypt/live/deinblog.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/deinblog.de/privkey.pem;

    # ActivityPub-spezifisch: keine Pufferung für Activity-Endpoints
    location ~* ^/(\.well-known|activitypub|ghost/activitypub) {
        proxy_pass http://localhost:2368;
        proxy_buffering off;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 30s;
    }

    location / {
        proxy_pass http://localhost:2368;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Der entscheidende Punkt: proxy_buffering off für ActivityPub-Endpoints verhindert, dass NGINX Responses puffert und dabei Headers verändert. proxy_read_timeout stellt sicher, dass Ghost genug Zeit hat, Aktivitäten zu verarbeiten, ohne dass NGINX die Verbindung vorzeitig schließt.


Fazit

Caddy ist ein exzellenter Proxy für viele Szenarien – einfache Web-Apps, schnelle Deployments, Situationen, in denen automatisches HTTPS ein echter Mehrwert ist. Für ActivityPub-basierte Anwendungen wie Ghost ist der interne Komplexität von Caddy jedoch ein Risikofaktor.

NGINX ist nicht sexy. Die Konfiguration ist wortreich, Certbot muss separat verwaltet werden, und HTTP/3 ist noch nicht produktionsreif. Aber NGINX tut genau das, was man ihm sagt – nicht mehr, nicht weniger. Für ein Protokoll wie ActivityPub, das auf Vorhersehbarkeit und HTTP-Konformität angewiesen ist, ist das eine Tugend, keine Schwäche.

Wenn deine Ghost-Instanz plötzlich aufhört zu föderieren, ist dein Reverse Proxy der erste Ort, den du untersuchen solltest.


Dieser Artikel basiert auf praktischen Erfahrungen im Betrieb von Ghost mit ActivityPub-Unterstützung sowie auf einer Analyse der Protokollspezifikationen von ActivityPub (W3C) und den jeweiligen Proxy-Dokumentationen.