End-to-End Request-Flow Analyse
Zeitmessung im Überblick
Bevor wir in die technischen Details eintauchen, schauen wir uns zunächst das Gesamtbild an: Wie lange dauert es eigentlich, bis diese Seite auf Deinem Bildschirm erscheint?
Phase Dauer Kumuliert ────────────────────────────────────────────────────── DNS-Auflösung - - (nicht im Messpfad) TCP-Verbindung 0.1 ms 0.1 ms TLS-Handshake 0.2 ms 0.3 ms (Session-Reuse) Time to First Byte 2.8 ms 3.1 ms (Redis-Cache) Inhalt übertragen 0.4 ms 3.5 ms ────────────────────────────────────────────────────── Gesamt (Server-intern) ~3.5 ms
Methodologie und Einordnung
Wichtiger Hinweis: Diese Werte wurden vom Server selbst gemessen (localhost) und zeigen die serverseitige Verarbeitung - nicht die Erfahrung eines echten Nutzers. DNS ist nicht im Messpfad enthalten, da der Server seine eigene Adresse kennt. Die Netzwerklatenz fehlt komplett.
Typische Größenordnungen für echte Nutzer
Die folgenden Werte sind grobe Orientierungen, keine Messwerte. Die tatsächliche Dauer hängt von vielen Faktoren ab: Internetanbieter, Netzwerkauslastung, Gerät, Cache-Zustand.
Komponente Abhängig von ────────────────────────────────────────────────────────────────── DNS-Auflösung Resolver-Cache, Provider, TTL Netzwerk-Latenz (RTT) Entfernung, Routing, ISP TLS-Handshake RTT-Vielfaches, Resumption-Status Server-Verarbeitung Cache-Status (siehe Messung oben) Content-Transfer Bandbreite, Kompression, Dateigröße
Die Server-Verarbeitung ist nur ein kleiner Teil des Gesamterlebnisses. Der Löwenanteil entfällt auf Netzwerk-Latenzen, die von Deinem Standort und Internetanbieter abhängen.
Vergleich mit anderen Systemen
Zum Kontext: Die Server-Verarbeitungszeit hängt stark von der Architektur ab. Ein gut konfiguriertes CMS mit aktivem Page-Cache erreicht ähnliche Werte. Ohne Caching liegen PHP-Anwendungen typischerweise höher - abhängig von Datenbankabfragen, Plugin-Anzahl und Hosting-Qualität.
Die folgenden Kapitel erklären Schritt für Schritt, wie der Request-Flow funktioniert.
Messung: = date("d.m.Y") ?>, Server-intern via curl (localhost), Redis-Cache aktiv.
Phase 1: DNS – Vom Namen zur Adresse
Wenn Du „karlkratz.de" in die Adresszeile eingibst, passiert etwas Interessantes: Dein Computer weiß nicht, wo diese Website liegt. Er kennt nur die IP-Adresse – eine Zahlenfolge wie 144.76.103.30. Die Übersetzung von Namen zu Zahlen übernimmt das Domain Name System, kurz DNS.
Stell Dir DNS wie ein riesiges Telefonbuch vor, das über das gesamte Internet verteilt ist. Wenn Du eine Nummer nachschlagen willst, fragt Dein Computer zunächst seinen lokalen Speicher (Cache), dann Deinen Router, dann Deinen Internet-Provider, und erst wenn niemand die Antwort kennt, werden die weltweiten DNS-Server befragt.
+─────────────────────────────────────────────────────────────+ │ DEIN BROWSER │ │ │ │ 1. Du tippst: karlkratz.de/end-2-end-analyse-webseite │ │ 2. Browser prüft DNS-Cache (Browser → OS → Router) │ │ 3. Falls nicht gecacht: DNS-Anfrage starten │ +────────────────────────────┬────────────────────────────────+ │ ▼ +─────────────────────────────────────────────────────────────+ │ DNS-RESOLVER │ │ │ │ Anfrage: karlkratz.de → A-Record (IPv4-Adresse) │ │ │ │ Rekursive Suche (falls nicht gecacht): │ │ ├── Root-Server (.) → „Frag .de-Server" │ │ ├── TLD-Server (.de) → „Frag Hetzner DNS" │ │ └── Autoritativer NS → 144.76.103.30 │ │ │ │ TTL: 86400 Sek. (Obergrenze für Caching, nicht garantiert)│ +─────────────────────────────────────────────────────────────+
DNS-Anfragen werden auf mehreren Ebenen gecacht – im Browser, im Betriebssystem, im Router und beim Provider. Die TTL (Time to Live) gibt eine Obergrenze vor, wie lange die Antwort gespeichert werden darf. In der Praxis kann die tatsächliche Cache-Dauer kürzer sein, abhängig von Resolver-Konfiguration und verfügbarem Speicher.
Bei Cache-Hit entfällt die DNS-Phase praktisch vollständig. Bei Cache-Miss hängt die Dauer vom Resolver und der Netzwerkstrecke ab – typischerweise im Bereich von wenigen bis einigen Dutzend Millisekunden.
Warum ist das wichtig? DNS ist oft ein versteckter Faktor bei der Ladezeit. Viele Websites verwenden externe Dienste (Google Analytics, Fonts, CDNs), die jeweils eigene DNS-Anfragen erfordern. Diese Seite lädt alles vom selben Server – eine bewusste Entscheidung für kürzere Ladezeiten und besseren Datenschutz.
Phase 2: TCP – Die Verbindung aufbauen
Nachdem Dein Browser die IP-Adresse kennt, muss er eine Verbindung zum Server herstellen. Das Internet verwendet dafür das „Transmission Control Protocol" (TCP) – ein Protokoll, das sicherstellt, dass Daten vollständig und in der richtigen Reihenfolge ankommen.
Bevor irgendwelche Daten fließen können, müssen sich Browser und Server erst einmal vorstellen. Das geschieht im sogenannten „Drei-Wege-Handshake" – einem kurzen Tanz aus drei Nachrichten, der die Verbindung aufbaut.
+──────────────+ +──────────────+ │ DEIN │ │ SERVER │ │ BROWSER │ │ 144.76.103.30│ │ │ │ Port 443 │ +──────┬───────+ +──────┬───────+ │ │ │ ──────── SYN ───────────────────────▶ │ │ „Hallo, ich möchte reden" │ │ │ │ ◀─────── SYN-ACK ─────────────────── │ │ „Hallo! Ich bin bereit." │ │ │ │ ──────── ACK ───────────────────────▶ │ │ „Verstanden, los geht's!" │ │ │ │ Verbindung hergestellt │ │ │ │ Dauer: 0.1 ms (lokal gemessen) │ │ Remote: abhängig vom RTT │ +──────┴──────────────────────────────────────────+
Warum drei Nachrichten? Weil beide Seiten sicherstellen müssen, dass sie senden UND empfangen können. Der erste SYN sagt: „Ich kann senden." Der SYN-ACK antwortet: „Ich kann empfangen und senden." Der letzte ACK bestätigt: „Ich kann auch empfangen." Erst dann ist die Verbindung symmetrisch.
Die Messung zeigt 0.1 ms – das ist extrem schnell, weil die Messung vom Server selbst erfolgte (localhost). Bei echten Nutzern dauert der Handshake mindestens einen RTT (Round-Trip-Time) – abhängig von der Entfernung zwischen Client und Server, der Routing-Qualität und dem Internetanbieter.
Phase 3: TLS – Verschlüsselung aktivieren
Jetzt wird es richtig interessant: Bevor irgendwelche Inhalte übertragen werden, müssen Browser und Server eine verschlüsselte Verbindung aushandeln. Das ist der TLS-Handshake („Transport Layer Security") – der Grund, warum in der Adresszeile ein Schloss erscheint und die URL mit „https" beginnt.
Verschlüsselung schützt Deine Daten vor neugierigen Blicken. Ohne TLS könnte jeder im selben Netzwerk (z.B. im Café-WLAN) mitlesen, was Du aufrufst. Mit TLS sieht ein Lauscher nur unlesbaren Datensalat.
+──────────────+ +──────────────+ │ DEIN │ │ SERVER │ │ BROWSER │ │ Apache + │ │ │ │ mod_ssl │ +──────┬───────+ +──────┬───────+ │ │ │ ───── ClientHello ──────────────────▶ │ │ + TLS 1.3 unterstützt │ │ + Schlüsselaustausch: X25519 │ │ + Server-Name: karlkratz.de │ │ │ │ ◀──── ServerHello ────────────────── │ │ + Zertifikat (Let's Encrypt) │ │ + Signatur-Beweis │ │ + Fertig! │ │ │ │ ───── Finished ─────────────────────▶ │ │ │ │ 🔒 Verschlüsselte Verbindung │ +──────┴──────────────────────────────────────────+
TLS 1.3 vs. TLS 1.2: Die Vorgängerversion TLS 1.2 benötigt zwei Roundtrips für den Handshake. TLS 1.3 reduziert das auf einen Roundtrip. Mit Session Resumption kann die erneute Verbindung noch schneller erfolgen. Die optionale 0-RTT-Funktion ermöglicht sofortigen Datenversand, wird hier aber nicht standardmäßig genutzt (Sicherheits-Trade-off).
Das Zertifikat kommt von Let's Encrypt – einer gemeinnützigen Organisation, die kostenlose Zertifikate ausstellt. Vor Let's Encrypt kosteten Zertifikate Geld und waren kompliziert einzurichten. Heute hat jede seriöse Website eines, und Browser warnen deutlich, wenn es fehlt.
Technisches Detail: Der Server verwendet ECDSA-Zertifikate mit elliptischen Kurven (statt RSA). Diese sind kompakter als vergleichbare RSA-Zertifikate. Je nach Client-Plattform kann das die Verifikation beschleunigen.
Phase 4: Die eigentliche Anfrage
Endlich! Die Verbindung steht, die Verschlüsselung ist aktiv – jetzt kann Dein Browser seine Anfrage stellen. Das Protokoll dafür heißt HTTP (Hypertext Transfer Protocol), und wir nutzen die moderne Version HTTP/2.
Eine HTTP-Anfrage ist im Kern sehr einfach: Der Browser sagt dem Server, welche Seite er haben möchte, und der Server antwortet mit dem Inhalt. Aber im Detail steckt mehr dahinter – Header mit Metainformationen, Cookies für Sitzungen, und Hinweise zur gewünschten Sprache und Format.
+─────────────────────────────────────────────────────────────+ │ HTTP/2 REQUEST │ +─────────────────────────────────────────────────────────────+ │ │ │ Pseudo-Header (HTTP/2-spezifisch): │ │ :method = GET │ │ :path = /end-2-end-analyse-webseite │ │ :authority = karlkratz.de │ │ :scheme = https │ │ │ │ Normale Header: │ │ accept : text/html, application/xhtml+xml, ... │ │ accept-encoding : gzip, deflate, br │ │ accept-language : de-DE, de;q=0.9, en;q=0.8 │ │ user-agent : Mozilla/5.0 (...) │ │ cookie : PHPSESSID=...; kk_sid=... │ │ │ +─────────────────────────────────────────────────────────────+
HTTP/2 vs. HTTP/1.1: Die alte Version schickte alles als lesbaren Text. HTTP/2 verwendet ein binäres Format, das effizienter ist. Außerdem kann HTTP/2 mehrere Anfragen gleichzeitig über eine Verbindung senden (Multiplexing) – früher brauchte jede Datei eine eigene Verbindung.
Beachte den Header „accept-encoding: br" – das „br" steht für Brotli, einen modernen Kompressionsalgorithmus. Der Browser signalisiert damit: „Hey Server, ich verstehe Brotli, bitte komprimiere die Antwort!" Der Server antwortet tatsächlich mit Brotli und reduziert so die übertragene Datenmenge deutlich.
Cookies und Datenschutz: Diese Website verwendet nur technisch notwendige Cookies (Sitzung, DSGVO-Consent). Kein Tracking durch Drittanbieter, keine Werbe-Cookies. Die Session-ID ist nur eine zufällige Zeichenkette, die Deine Anmeldung speichert – falls Du eingeloggt bist.
Phase 5: Der Webserver nimmt entgegen
Die Anfrage erreicht den Server und wird von Apache empfangen – einem Webserver, der seit 1995 das Rückgrat des Internets bildet. Apache ist wie ein erfahrener Concierge: Er nimmt Anfragen entgegen, prüft die Berechtigung, und leitet an die richtige Stelle weiter.
Bevor Apache die Anfrage an PHP weitergibt, durchläuft sie mehrere Prüfungen. Ist die URL erlaubt? Handelt es sich um einen bekannten Angriff? Soll die Datei komprimiert werden? Diese Regeln sind in der Konfigurationsdatei .htaccess definiert.
+─────────────────────────────────────────────────────────────+ │ APACHE VERARBEITUNG │ +─────────────────────────────────────────────────────────────+ │ │ │ 1. VirtualHost-Zuordnung │ │ ├── ServerName: karlkratz.de │ │ └── DocumentRoot: /var/www/prod.karlkratz.de/public │ │ │ │ 2. mod_rewrite – URL-Regeln │ │ ├── Blockieren: /wp-admin, /.env, /.git, /xmlrpc │ │ ├── Statische Dateien direkt ausliefern │ │ └── Alles andere → index.php (Front Controller) │ │ │ │ 3. mod_brotli – Kompression │ │ └── HTML, CSS, JS → Brotli-komprimiert │ │ │ │ 4. Sicherheits-Header │ │ ├── X-Content-Type-Options: nosniff │ │ ├── X-XSS-Protection: 1; mode=block │ │ ├── Strict-Transport-Security (HSTS) │ │ └── Referrer-Policy: strict-origin-when-cross-origin │ │ │ │ Dauer: ~0.2 ms │ +────────────────────────────┬────────────────────────────────+ │ ▼ +───────────────────────────+ │ PHP-FPM Socket │ │ /run/php/php8.4-fpm.sock │ +───────────────────────────+
Sicherheit durch Ausschluss: Die Rewrite-Regeln blockieren bekannte Angriffsmuster. Hacker scannen das Internet nach WordPress-Installationen (/wp-admin) oder versehentlich hochgeladenen Konfigurationsdateien (.env, .git). Hier führen solche Anfragen ins Leere – ein schnelles „403 Forbidden" ohne weitere Verarbeitung.
Front Controller Pattern: Alle dynamischen Anfragen landen bei index.php. Das ist ein bewährtes Architekturmuster: Ein einziger Einstiegspunkt kontrolliert den gesamten Anwendungsfluss. Keine versteckten PHP-Dateien, keine ungeschützten Endpunkte.
MPM Event: Apache kann in verschiedenen Modi laufen. „Event" ist der modernste und effizienteste – er nutzt asynchrone Verarbeitung und kann tausende gleichzeitige Verbindungen mit wenig Speicher bedienen. Früher war „Prefork" üblich, bei dem für jede Verbindung ein eigener Prozess startete – ein Speicherfresser.
Phase 6: PHP startet die Anwendung
Jetzt übernimmt PHP – die Programmiersprache, in der diese Website geschrieben ist. PHP-FPM (FastCGI Process Manager) hält mehrere PHP-Prozesse bereit, sodass Anfragen sofort bearbeitet werden können, ohne auf den Start eines neuen Prozesses zu warten.
Der erste Schritt ist das „Bootstrap" – das Hochfahren der Anwendung. Dabei werden Konfigurationen geladen, der Autoloader registriert (damit Klassen automatisch gefunden werden), und die Datenbankverbindung vorbereitet. Klingt aufwendig, dauert aber nur etwa eine Millisekunde.
+─────────────────────────────────────────────────────────────+ │ /var/www/prod.karlkratz.de/public/index.php │ +─────────────────────────────────────────────────────────────+ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 1. Bootstrap laden │ │ │ │ require '../src/bootstrap.php' │ │ │ │ │ │ │ │ ├── .env laden (Umgebungsvariablen) │ │ │ │ ├── Autoloader registrieren │ │ │ │ │ └── App\, Shared\, Controllers\ ... │ │ │ │ ├── Credentials: /var/credentials/karlkratz/*.json │ │ │ │ ├── ConfigLoader: tenant_config (MariaDB) │ │ │ │ └── Session starten (nur Web, nicht CLI) │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ Dauer: ~1 ms │ +─────────────────────────────────────────────────────────────+
OPcache macht's schnell: PHP-Code wird normalerweise bei jedem Aufruf neu interpretiert. OPcache speichert den kompilierten Bytecode im Arbeitsspeicher – beim nächsten Aufruf entfällt das Parsen komplett. Das spart bei jeder Anfrage mehrere Millisekunden.
Credentials außerhalb des Codes: Passwörter und API-Keys liegen nicht im Anwendungsverzeichnis, sondern unter /var/credentials/. Selbst wenn jemand den Quellcode lesen könnte, fände er keine Zugangsdaten. Das ist ein wichtiges Sicherheitsprinzip: Trennung von Code und Konfiguration.
ConfigLoader – Dynamische Einstellungen: Viele Einstellungen liegen nicht in Dateien, sondern in der Datenbank (Tabelle tenant_config). Das ermöglicht Änderungen zur Laufzeit, ohne Dateien zu bearbeiten oder den Server neu zu starten.
Phase 7: Sicherheitsprüfungen
Bevor die eigentliche Seite gerendert wird, durchläuft die Anfrage mehrere Sicherheitsschichten. Das Internet ist kein freundlicher Ort – ständig scannen Bots nach Schwachstellen, versuchen Spammer ihre Links zu platzieren, und gelegentlich probieren Angreifer gezielte Attacken.
Diese Website verwendet ein mehrstufiges Abwehrsystem: Verdächtige IPs werden erkannt und in „Quarantäne" gesetzt, Bots werden identifiziert und bei Bedarf blockiert, und die Anzahl der Anfragen pro Zeiteinheit wird begrenzt (Rate Limiting).
+─────────────────────────────────────────────────────────────+ │ SICHERHEITSPRÜFUNG │ +─────────────────────────────────────────────────────────────+ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 1. SuspectService – IP-Prüfung │ │ │ │ ├── Whitelist prüfen (bekannte gute IPs) │ │ │ │ ├── Quarantäne-Status abfragen │ │ │ │ ├── ✓ allow → weiter zur nächsten Prüfung │ │ │ │ ├── ⚠ challenge → Umleitung zu /bleib-mensch │ │ │ │ └── ✗ block → 403 Forbidden │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 2. BotDetectionService – Bot-Erkennung │ │ │ │ ├── User-Agent analysieren │ │ │ │ ├── Reverse DNS + Forward DNS Bestätigung │ │ │ │ └── Bekannte Bots: Googlebot, Bingbot, GPTBot ✓ │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 3. Rate Limiting – Überlastschutz │ │ │ │ └── Max. Anfragen pro IP und Zeitfenster │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ Dauer: ~0.3 ms (mit APCu-Cache) │ +─────────────────────────────────────────────────────────────+
Das „Bleib Mensch"-System: Wenn eine IP verdächtig erscheint (zu viele Anfragen, bekanntes Bot-Muster), wird sie nicht sofort gesperrt, sondern zu einer Challenge-Seite geleitet. Dort muss der Besucher beweisen, dass er ein Mensch ist – ähnlich wie bei CAPTCHAs, aber freundlicher gestaltet.
Suchmaschinen willkommen: Google, Bing und andere legitime Crawler werden nicht blockiert – sie sind wichtig für die Auffindbarkeit. Der BotDetectionService verifiziert Crawler per Reverse DNS gefolgt von Forward DNS Bestätigung: Erst wird die IP zu einem Hostnamen aufgelöst, dann wird geprüft, ob dieser Hostname zurück zur ursprünglichen IP führt. Ohne diese Bestätigung wäre ein Reverse DNS allein fälschbar.
Fail-Open vs. Fail-Closed: Die Sicherheitsprüfungen unterscheiden sich im Fehlerfall:
- Fail-Open: Reputation-Prüfung, Rate-Limiting – bei technischen Problemen wird Zugriff erlaubt
- Fail-Closed: Authentifizierung, Zugriffsrechte – bei Fehlern wird Zugriff verweigert
Für eine öffentliche Website ist Fail-Open bei Reputation-Checks vertretbar. Systeme mit sensiblen Daten würden hier anders entscheiden.
Phase 8: Der Cache – Geheimnis der Geschwindigkeit
Hier passiert die eigentliche Magie: Statt die Seite bei jedem Aufruf neu zu rendern, prüft das System zunächst, ob eine fertige Version im Cache liegt. Dieser Cache besteht aus zwei Schichten – schnell und sehr schnell.
Warum zwei Cache-Ebenen? Redis (Layer 1) läuft komplett im Arbeitsspeicher und antwortet in unter einer Millisekunde. Falls Redis ausfällt oder die Daten dort fehlen, greift Layer 2: eine MariaDB-Tabelle, die zwar langsamer ist, aber persistent auf der Festplatte liegt. So bleibt die Seite auch bei Ausfall von Redis auslieferbar.
+─────────────────────────────────────────────────────────────+ │ CACHE-STRATEGIE │ │ (Nur für anonyme Besucher!) │ +─────────────────────────────────────────────────────────────+ │ │ │ Authentifiziert? │ │ ├── JA → SLOW PATH (immer frisch rendern) │ │ └── NEIN → FAST PATH (Cache nutzen) ↓ │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ LAYER 1: Redis (RAM, sub-millisecond) │ │ │ │ RedisCacheService::getPage('end-2-end-...') │ │ │ │ │ │ │ │ ├── HIT + gültig → HTML sofort zurück ⚡ │ │ │ │ ├── HIT + veraltet → weiter zu Layer 2 │ │ │ │ └── MISS → weiter zu Layer 2 │ │ │ │ │ │ │ │ Auto-Invalidierung: │ │ │ │ Wenn Artefakt geändert → Cache ungültig │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ LAYER 2: MariaDB (artefakt_routes Tabelle) │ │ │ │ SELECT compiled_html FROM artefakt_routes │ │ │ │ WHERE slug = 'end-2-end-analyse-webseite' │ │ │ │ │ │ │ │ ├── HIT + gültig → HTML + Write-Through zu Redis │ │ │ │ └── MISS → SLOW PATH (neu rendern) │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ Cache-HIT: ~0.5 ms │ Cache-MISS: +8 ms (Rendering) │ +─────────────────────────────────────────────────────────────+
Warum nicht einfach alles cachen? Eingeloggte Benutzer sehen personalisierte Inhalte – ihren Namen, ihre Mitgliedschaft, eventuell geschützte Bereiche. Diese Inhalte dürfen nicht gecacht werden, sonst würde Benutzer A die Daten von Benutzer B sehen. Deshalb: Anonyme Besucher bekommen den schnellen Cache, authentifizierte Benutzer bekommen frisch gerenderte Seiten.
Write-Through: Wenn Layer 2 (MariaDB) einen Treffer hat, wird das Ergebnis automatisch in Layer 1 (Redis) geschrieben. Beim nächsten Aufruf ist Redis wieder warm. Dieses Muster heißt „Write-Through" und sorgt für selbstheilende Caches.
Automatische Invalidierung: Die größte Herausforderung beim Caching ist: Woher weiß der Cache, wann er ungültig ist? Hier wird bei jeder Änderung eines Artefakts ein Zeitstempel aktualisiert. Der Cache vergleicht seinen eigenen Zeitstempel mit dem der Abhängigkeiten – stimmen sie nicht überein, wird neu gerendert.
Phase 9: Die Seite wird zusammengebaut
Falls der Cache leer ist (Cache-Miss), beginnt der aufwendigste Teil: Das Rendering. Die Seite besteht nicht aus einer einzigen HTML-Datei, sondern wird aus vielen kleinen Bausteinen zusammengesetzt – Header, Footer, Inhaltsblöcke, Stylesheets, Scripts.
Das Herzstück ist der ArtefaktRenderer. Er liest das Seiten-Template aus der Datenbank und ersetzt Platzhalter wie { {content:header} } durch die tatsächlichen Inhalte. Das passiert rekursiv: Ein Baustein kann wieder Platzhalter enthalten, die wiederum aufgelöst werden müssen.
+─────────────────────────────────────────────────────────────+ │ Seite laden: artefakte WHERE slug='end-2-end-...' │ +─────────────────────────────────────────────────────────────+ │ │ │ Template-Struktur auflösen: │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ { {content:html-head-db} } │ │ │ │ └── <html><head>...<link css>...</head><body> │ │ │ │ │ │ │ │ { {php:header} } │ │ │ │ └── Views/components/header.php ausführen │ │ │ │ └── Navigation, Auth-Zustand prüfen │ │ │ │ │ │ │ │ { {content:logo-hero} } │ │ │ │ └── Hero-Bereich mit Logo │ │ │ │ │ │ │ │ { {article:end-2-end-...~body} } │ │ │ │ └── Body-Artefakt laden │ │ │ │ └── 16 Kapitel-Artefakte laden │ │ │ │ └── Markdown → HTML konvertieren │ │ │ │ │ │ │ │ { {content:footer-main} } │ │ │ │ └── Footer-Links │ │ │ │ │ │ │ │ { {content:html-foot} } │ │ │ │ └── </body></html>, First-Party Tracking │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ Nach dem Rendering: Cache in Redis + MariaDB speichern │ │ │ │ Dauer: ~8 ms (inkl. 20+ DB-Queries, warmem Buffer Pool) │ +─────────────────────────────────────────────────────────────+
Artefakte statt Dateien: Klassische Websites speichern Inhalte in Dateien auf der Festplatte. Hier liegen alle Inhalte in der Datenbank (Tabelle „artefakte"). Das hat Vorteile: Versionierung, Volltextsuche, dynamische Berechtigungen, und keine Probleme mit Dateisystem-Berechtigungen.
Markdown für Autoren: Die Kapitel dieser Seite sind in Markdown geschrieben – einer einfachen Formatierung, die leichter zu lesen und schreiben ist als HTML. Der ArtefaktRenderer wandelt Markdown automatisch in HTML um (mit der Parsedown-Bibliothek).
Dependency Tracking: Der Renderer merkt sich, welche Artefakte für eine Seite verwendet wurden. Diese Abhängigkeiten werden in der Tabelle „artefakt_dependencies" gespeichert. Wenn später ein Baustein geändert wird, weiß das System, welche Seiten neu gerendert werden müssen.
Phase 10: Die Antwort geht zurück
Die Seite ist fertig gerendert – jetzt muss sie zum Browser. Die HTTP-Antwort enthält nicht nur das HTML, sondern auch wichtige Metadaten in den Response-Headern: Kompressionsformat, Sicherheitseinstellungen, Caching-Anweisungen.
Besonders interessant ist die Kompression: Das ursprüngliche HTML dieser Seite ist etwa 45 KB groß. Mit Brotli-Kompression werden daraus etwa 12 KB. Weniger Daten bedeuten schnellere Übertragung, besonders bei langsamen Mobilfunkverbindungen.
+─────────────────────────────────────────────────────────────+ │ HTTP/2 RESPONSE │ +─────────────────────────────────────────────────────────────+ │ :status = 200 (OK – Seite gefunden und gerendert) │ │ │ │ Inhalts-Header: │ │ content-type : text/html; charset=UTF-8 │ │ content-encoding : br (Brotli-komprimiert) │ │ content-length : ~12 KB (komprimiert, ~45 KB roh) │ │ │ │ Cache-Steuerung: │ │ cache-control : no-store, no-cache, must-revalidate │ │ (HTML nicht im Browser cachen – serverseitig kontrolliert)│ │ │ │ Sicherheits-Header: │ │ x-content-type-options : nosniff │ │ x-xss-protection : 1; mode=block (Legacy) │ │ referrer-policy : strict-origin-when-cross-origin│ │ strict-transport-security: max-age=31536000 (HSTS) │ │ permissions-policy : geolocation=(), microphone=() │ │ │ │ Session: │ │ set-cookie: PHPSESSID=...; HttpOnly; Secure; SameSite=Lax │ +─────────────────────────────────────────────────────────────+
Warum „no-cache" für HTML? Das klingt paradox: Wir haben einen ausgeklügelten Server-Cache, aber der Browser soll HTML nicht cachen? Der Grund: Serverseitiges Caching ist kontrollierbar. Wir wissen genau, wann Inhalte aktualisiert wurden, und invalidieren den Cache entsprechend. Für statische Assets (CSS, JS, Bilder) mit Fingerprint im Dateinamen ist langfristiges Browser-Caching dagegen sinnvoll.
Sicherheits-Header erklärt: Diese Header schützen vor verschiedenen Angriffen. „nosniff" verhindert, dass Browser Dateitypen raten. „HSTS" zwingt den Browser, immer HTTPS zu verwenden (auch wenn jemand nur „http://" tippt). „permissions-policy" deaktiviert APIs, die diese Seite nicht braucht (Mikrofon, Kamera, Standort). Der X-XSS-Protection-Header ist ein Legacy-Header mit begrenzter Wirkung in modernen Browsern – der eigentliche XSS-Schutz erfolgt durch Content Security Policy und sauberes Output-Encoding.
Cookie-Sicherheit: Das Session-Cookie hat drei Schutzflags: „HttpOnly" (JavaScript kann es nicht lesen), „Secure" (nur über HTTPS), „SameSite=Lax" (Schutz vor Cross-Site-Request-Forgery). Diese Kombination ist heute Standard für sichere Websites.
Phase 11: Der Browser zeigt die Seite an
Die HTML-Daten sind angekommen – aber Du siehst noch nichts. Jetzt beginnt die Arbeit des Browsers: Er muss das HTML parsen, CSS laden und anwenden, Bilder nachladen, JavaScript ausführen, und schließlich die Pixel auf den Bildschirm malen.
Dieser Prozess heißt „Critical Rendering Path" und ist der Grund, warum manche Websites gefühlt langsam sind, obwohl der Server schnell antwortet. Eine Seite kann in 10 ms geliefert werden, aber wenn sie 50 JavaScript-Dateien nachladen muss, dauert es trotzdem Sekunden, bis sie interaktiv ist.
+─────────────────────────────────────────────────────────────+ │ BROWSER RENDERING │ +─────────────────────────────────────────────────────────────+ │ │ │ 1. HTML parsen → DOM-Baum aufbauen │ │ └── Streaming: Parsen während des Downloads │ │ │ │ 2. CSS laden (blockiert Rendering!) │ │ ├── /assets/css/db.css │ │ └── /assets/css/terminal-style.css │ │ │ │ 3. CSSOM aufbauen │ │ └── CSS → CSS Object Model │ │ │ │ 4. Render Tree erstellen │ │ └── DOM + CSSOM → Was wird wie angezeigt? │ │ │ │ 5. Layout berechnen │ │ └── Position und Größe jedes Elements │ │ │ │ 6. Malen (Paint) │ │ └── Pixel auf den Bildschirm zeichnen │ │ │ │ 7. Compositing │ │ └── Ebenen zusammenfügen (GPU-beschleunigt) │ │ │ │ 8. JavaScript ausführen (async/defer) │ │ └── /assets/js/main.js (minimal) │ +─────────────────────────────────────────────────────────────+
Warum lädt diese Seite schnell? Sie vermeidet die üblichen Fallstricke: Kein jQuery (früher auf fast jeder Website), keine externen Fonts (Google Fonts verursachen zusätzliche DNS-Lookups und Downloads), keine Werbe-Tracker, kein Cookie-Banner von Drittanbietern. Nur das Nötigste – und das vom eigenen Server.
CSS vor JavaScript: CSS blockiert das Rendering – der Browser zeigt nichts an, bis alle Stylesheets geladen sind. JavaScript blockiert (ohne async/defer) sogar das HTML-Parsen. Deshalb werden hier alle Scripts mit „defer" geladen: Der Browser lädt sie parallel, führt sie aber erst nach dem HTML-Parsen aus.
Core Web Vitals: Google bewertet Websites nach Metriken wie LCP (Largest Contentful Paint) und CLS (Cumulative Layout Shift). Diese Seite ist so gestaltet, dass gute Werte erreichbar sind: wenig externe Ressourcen, keine Layout-Verschiebungen durch nachladende Inhalte. Die tatsächlichen Werte hängen vom Gerät und Netzwerk des Nutzers ab – Verifizierung erfolgt über Tools wie PageSpeed Insights.
Der vollständige Ablauf im Überblick
Nachdem wir jede Phase einzeln betrachtet haben, zeigt dieses Diagramm den gesamten Ablauf auf einen Blick. Von links nach rechts siehst Du die beteiligten Komponenten: Deinen Browser, den DNS-Server, und auf der Server-Seite Apache, PHP, Redis und MariaDB.
Die Pfeile zeigen, wie die Anfrage durch das System wandert. Bei einem Cache-Hit (dem Normalfall) ist der Weg kurz: Browser → Apache → PHP → Redis → zurück. Bei einem Cache-Miss werden zusätzlich MariaDB-Abfragen nötig.
Browser DNS Apache PHP Redis MariaDB │ │ │ │ │ │ │──Query→│ │ │ │ │ │←──IP───│ │ │ │ │ │ │ │ │ │ │ │════ TCP + TLS ══════════▶│ │ │ │ │ │ │ │ │ │══════ GET /end-2-end-... ▶│ │ │ │ │ │────────▶│ │ │ │ │ │ │ │ │ │ │ │ │ Security Check │ │ │ │ │ │ │ │ │ │ │──GET──▶│ │ │ │ │ │◀─HIT───│ │ Cache-Hit! │ │ │ │ │ │ │ │ │◀───────│ │ │ │ │◀───────│ │ │ │ │◀═══════ HTTP 200 ════════│ │ │ │ │ │ │ │ │ │ Render │ │ │ │ │ │ │ │ │ │ │ Legende: ═══ Verschlüsselte Verbindung (HTTPS) ─── Interne Kommunikation ◀▶ Anfrage/Antwort
Der Cache macht den Unterschied: Im Diagramm siehst Du, dass bei einem Cache-Hit die MariaDB gar nicht kontaktiert wird. Redis antwortet aus dem Arbeitsspeicher – das spart die Datenbankabfrage und damit wertvolle Millisekunden. Bei 100 Besuchern pro Sekunde wären das 100 gesparte Datenbankabfragen pro Sekunde.
Parallele Möglichkeiten: HTTP/2 erlaubt es dem Browser, mehrere Ressourcen gleichzeitig anzufordern. Wenn diese Seite CSS und JavaScript nachlädt, passiert das parallel – nicht nacheinander wie bei HTTP/1.1. Das Diagramm zeigt vereinfacht nur den HTML-Abruf, aber in der Praxis laufen mehrere Streams gleichzeitig.
Die technische Infrastruktur
Hinter jeder schnellen Website steckt solide Infrastruktur. Diese Seite läuft auf einem dedizierten Server bei Hetzner in Deutschland – keine Cloud-Instanz, sondern echte Hardware. Das bringt vorhersagbare Leistung ohne die Variabilität virtualisierter Umgebungen.
Die folgende Tabelle zeigt die wichtigsten Komponenten und ihre Versionen. Die eingesetzten Komponenten entsprechen einem aktuellen Stable-Stand. Veraltete Software ist nicht nur langsamer, sondern auch ein Sicherheitsrisiko.
Komponente Version / Details ────────────────────────────────────────────────────────────── Hosting Hetzner Dedicated Server (Deutschland) Betriebssystem Debian 13 „Trixie" (Stand: = date("m/Y") ?>) IP-Adresse 144.76.103.30 Kernel Linux 6.12 ────────────────────────────────────────────────────────────── Webserver Apache 2.4.66 (MPM Event) PHP 8.4.16 (FPM, OPcache aktiviert) Datenbank MariaDB 11.8.3 Cache Redis 7.x (localhost:6379) ────────────────────────────────────────────────────────────── TLS Let's Encrypt (ECDSA, Issuer E8) Protokoll HTTP/2, TLS 1.3 Kompression Brotli (mod_brotli) DNS Hetzner DNS
Warum dedizierter Server statt Cloud? Cloud-Instanzen (AWS, Google Cloud) sind flexibel, aber teilen sich Hardware mit anderen Kunden. Das führt zu „noisy neighbors" – wenn der Nachbar Last erzeugt, leidet die eigene Performance. Ein dedizierter Server gehört Dir allein und liefert konstante Leistung.
Standort Deutschland: Der Server steht in Falkenstein (Sachsen). Für Besucher aus Deutschland bedeutet das kürzere Netzwerkwege. Außerdem unterliegt der Server deutschem Datenschutzrecht – wichtig für DSGVO-Konformität.
Aktuelle Software: PHP 8.4 ist deutlich schneller als ältere Versionen (JIT-Compiler, optimierte Datenstrukturen). MariaDB 11 bringt bessere Query-Optimierung. Debian wird als stabiles Release genutzt – kein Rolling Release, sondern feste Versionen mit regelmäßigen Sicherheitsupdates.
Performance-Optimierungen
Geschwindigkeit entsteht nicht durch eine einzelne Maßnahme, sondern durch viele kleine Optimierungen, die sich addieren. Die folgende Übersicht zeigt, welche Techniken hier zum Einsatz kommen.
Optimierung Effekt ────────────────────────────────────────────────────────────── [✓] 2-Layer-Cache (Redis + MariaDB) Schnelle Server-Antwort [✓] Brotli-Kompression Reduzierte Datenmenge [✓] HTTP/2 Multiplexing Parallele Streams [✓] TLS 1.3 Schnellerer Handshake [✓] PHP OPcache Kein Code-Parsen [✓] Prepared Statements Sichere Parametrisierung [✓] Keep-Alive Connections Verbindungen wiederverwenden [✓] Auto-Invalidierung Cache immer aktuell [✓] Keine externen Ressourcen Kein DNS-Overhead [✓] Minimales JavaScript Schnelles Time-to-Interactive
Cache-Strategie im Detail
Schicht Inhalt Invalidierung Latenz ────────────────────────────────────────────────────────────── Redis HTML (RAM) Auto (Zeitstempel) <1 ms MariaDB HTML (Disk) Auto (Zeitstempel) ~2 ms Browser Assets Fingerprint (langfristig) 0 ms
HTML vs. Assets: HTML-Seiten werden serverseitig gecacht, aber nicht im Browser (no-cache). So behalten wir Kontrolle über Aktualisierungen. Statische Assets (CSS, JS, Bilder) bekommen dagegen einen Fingerprint im Dateinamen und können langfristig im Browser gecacht werden.
Sicherheit als Querschnittsthema
Sicherheit wird hier über mehrere Schichten umgesetzt. Selbst wenn eine Schicht versagt, fangen die anderen den Angriff ab.
┌─────────────────────────────────────────────────────────────┐ │ Schicht 1: Netzwerk (nftables) │ │ └── IP-Blocking, Geo-Filtering, Rate-Limiting │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ Schicht 2: TLS (mod_ssl) │ │ └── HSTS, starke Cipher-Suites, Certificate Transparency │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ Schicht 3: Apache (mod_rewrite) │ │ └── Angriffsmuster blockieren (WordPress, .env, .git) │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ Schicht 4: PHP-Anwendung │ │ ├── SuspectService (Quarantäne + Challenge) │ │ ├── BotBlockingMiddleware (Rate-Limiting) │ │ └── SecurityHeaderService (CSP, XSS-Schutz) │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ Schicht 5: Datenbank │ │ └── Prepared Statements, Least-Privilege-Zugriff │ └─────────────────────────────────────────────────────────────┘
Defense in Depth: Jede Schicht hat eine Aufgabe. Die Firewall filtert offensichtlich böswilligen Traffic. TLS verschlüsselt alles. Apache blockiert bekannte Angriffspfade. PHP prüft Verhalten. Die Datenbank verwendet ausschließlich Prepared Statements – der zentrale Baustein gegen SQL-Injection. Korrekte Nutzung bleibt Voraussetzung; dynamische SQL-Teile oder unsichere Identifier erfordern zusätzliche Absicherung.
Stand: = date("d.m.Y") ?>
Wie aufwendig ist dieses System?
Nach all den technischen Details fragst Du Dich vielleicht: Wie viel Code steckt eigentlich hinter dieser Seite? Ist das ein überschaubares Projekt oder ein riesiges Framework? Die Antwort liegt irgendwo dazwischen – und sie verrät einiges über die Philosophie hinter dem System.
Die folgende Analyse zeigt alle PHP-Dateien, die an der Auslieferung dieser einen Seite beteiligt sind. Nicht die gesamte Codebasis, sondern nur der „Hot Path" – der Code, der bei jedem Seitenaufruf durchlaufen wird.
Kategorie Dateien Lines of Code ────────────────────────────────────────────────────────────── Einstiegspunkt └── index.php 1 361 Bootstrap & Konfiguration ├── bootstrap.php 1 350 ├── ConfigLoader.php 1 287 └── Config.php 1 290 ────────── ───── 3 927 Sicherheit & Middleware ├── SuspectService.php 1 570 ├── BotDetectionService.php 1 1.046 ├── RateLimitingService.php 1 421 └── BotBlockingMiddleware.php 1 80 ────────── ───── 4 2.117 Caching └── RedisCacheService.php 1 424 Controller & Rendering ├── ArtefaktController.php 1 175 ├── ArtefaktRenderer.php 1 698 ├── PlaceholderParser.php 1 876 └── PageRenderer.php 1 746 ────────── ───── 4 2.495 Datenbank └── DatabaseManager.php 1 681 HTTP & Headers ├── SecurityHeaderService.php 1 146 └── TrackingService.php 1 1.000 ────────── ───── 2 1.146 Templates & Views ├── header.php 1 236 ├── footer.php 1 66 └── Templates (3x) 3 ~300 ────────── ───── 5 ~602 Hilfsbibliotheken ├── SessionMembershipHelper 1 289 ├── AuthHelper.php 1 ~150 ├── Logger / SystemLogger 2 ~400 ├── GeoIPService.php 1 ~300 └── UserAgentParser.php 1 ~400 ────────── ───── 6 ~1.539 ══════════════════════════════════════════════════════════════ GESAMT 27 ~9.600 LOC
Knapp 10.000 Zeilen Code – das klingt nach viel, ist aber für eine vollwertige Webanwendung eher schlank. Zum Vergleich des reinen Codeumfangs (nicht des Funktionsumfangs): Das Framework Laravel umfasst über 400.000 Zeilen. WordPress hat mehr als 600.000. Hier wurde bewusst auf ein Framework verzichtet und stattdessen genau das gebaut, was für diesen Anwendungsfall gebraucht wird.
Die größten Komponenten erklärt
Datei LOC Aufgabe ────────────────────────────────────────────────────────────── BotDetectionService.php 1.046 Bot-Erkennung, DNS-Checks, Whitelist/Blacklist, Geo-IP TrackingService.php 1.000 Analytics, Geo-Lokation, Device-Erkennung, Dedup PlaceholderParser.php 876 Template-Syntax parsen, Verschachtelung auflösen PageRenderer.php 746 Seiten-Discovery, Template- Auswahl, Orchestrierung ArtefaktRenderer.php 698 Rekursives Rendering, Markdown, Schutzstufen
Sicherheit und Tracking dominieren: Über 2.000 Zeilen (20%) des Hot-Path-Codes dienen der Sicherheit. Das zeigt die Prioritäten: Eine Website muss sich gegen Angriffe wehren können, und das erfordert substantiellen Code. Der TrackingService ist ausführlich, weil er zuverlässig sein muss – Ausfälle der Datenbank dürfen nie den Seitenaufruf stören.
Was macht den Code schlank?
Kein Framework-Overhead: Frameworks wie Laravel oder Symfony bringen Komfort, aber auch Tausende von Dateien, die bei jedem Request geladen werden könnten. Hier gibt es keine ORM-Abstraktionen, keine Event-Dispatcher, keine Dependency-Injection-Container – nur direkten, verständlichen Code.
Single Responsibility: Jede Datei hat eine klare Aufgabe. Der ArtefaktRenderer rendert Artefakte. Der BotDetectionService erkennt Bots. Kein „GodClass" mit Tausenden Zeilen, die alles kann, aber niemand versteht.
Bewusster Verzicht: Features, die nicht gebraucht werden, wurden nicht gebaut. Kein Benutzer-Registrierungssystem (Authentifizierung läuft über Magic Links), kein Kommentarsystem, kein Shop. Weniger Code bedeutet weniger Bugs und weniger Angriffsfläche.
Das Wichtigste zum Schluss
Diese ~10.000 Zeilen Code ermöglichen serverseitige Antwortzeiten im einstelligen Millisekundenbereich, halten Angreifer fern, tracken Besucher datenschutzkonform, und rendern Inhalte aus einer flexiblen Artefakt-Datenbank. Jede Zeile hat einen Zweck.
Analyse erstellt am = date("d.m.Y") ?> für diese Seite: /end-2-end-analyse-webseite