Netzwerk und HTTP: TLS und Zertifikate, HTTP-Caching, Proxy und Load Balancer

Track Konzepte · Betriebssystem und Netzwerk · ca. 50 Min.

Was du aus Teil a brauchst

Zwischen Enter und Seite liegen DNS, TCP, TLS und HTTP, und jeder Schritt kostet Rundläufe (RTT). Eine TTL sagt einem Cache, wie lange eine Antwort gilt, und dasselbe Prinzip kehrt hier beim HTTP-Cache wieder. Falls das neu ist: Teil 1.

Worum es geht

Die Verbindung steht, jetzt kommt das, was im Betrieb am häufigsten Ärger macht: abgelaufene Zertifikate, Caches, die zu lange oder gar nicht greifen, und Lastverteiler, die die falschen Server belasten. Am Ende dieses Teils kannst du erklären, was eine Zertifikatskette (certificate chain) prüft und warum Rotation automatisch laufen muss, du hast selbst eine Cache-Entscheidung (Cache-Control, ETag, 304) programmiert und kannst begründen, wann ein L7 statt eines L4 Load Balancers gebraucht wird.

Auch hier ist alles eine Simulation: Zeit ist eine Zahl (Tage oder Sekunden), Zertifikate sind Dicts ohne Kryptografie, Server sind Zähler.

Plane ehrlich 50 Minuten ein: etwa 22 Minuten Lesen, 28 Minuten Übungen (drei Stück). Eine gute Pause ist nach Übung 2.

Von JS/TS her gedacht

Baustein JS/TS (Frontend oder Node) Python
Cache steuern res.setHeader("Cache-Control", "max-age=60") headers["Cache-Control"] = "max-age=60"
Cache umgehen fetch(url, { cache: "no-store" }) Header Cache-Control: no-cache mitschicken
Zertifikat prüfen Node: https.request prüft standardmäßig, rejectUnauthorized: false schaltet es ab ssl.create_default_context() prüft standardmäßig, check_hostname = False schaltet es ab
Revalidieren fetch(url, { headers: { "If-None-Match": etag } }) Header If-None-Match setzen

Beides Abschalten ist in Tutorials beliebt und in Produktion ein Fehler. Warum, siehst du gleich.

Konzept 1: TLS und Zertifikate

TLS (Transport Layer Security) macht aus einer offenen TCP-Verbindung eine verschlüsselte. Es leistet drei Dinge: Vertraulichkeit (niemand auf dem Weg liest mit), Integrität (niemand verändert unbemerkt) und Authentizität (du sprichst wirklich mit shop.beispiel.de). Die ersten beiden kommen aus Schlüsseln, die beim Handshake ausgehandelt werden (bei TLS 1.3 in einem Rundlauf). Die dritte kommt vom Zertifikat (certificate): ein Dokument, das sagt “dieser öffentliche Schlüssel gehört zu diesem Namen”, unterschrieben von einer Certificate Authority (CA, Zertifizierungsstelle).

Wer glaubt der CA? Dein Browser oder dein Betriebssystem hat eine Liste von Vertrauensankern (trust anchors, Root-Zertifikaten). Die CA, die dein Server-Zertifikat unterschreibt, ist meist eine Zwischen-CA (intermediate), die wiederum von einer Wurzel-CA unterschrieben ist. So entsteht die Zertifikatskette (certificate chain): Server-Zertifikat, dann Zwischenzertifikat, dann Wurzel in der Vertrauensliste. Der Client prüft: Passt der Name zum Host? Ist jedes Zertifikat gültig (Zeitraum)? Führt die Kette Glied für Glied (Aussteller des einen ist Inhaber des nächsten) zu einem Anker, dem er vertraut?

Das als Code mit simulierten Zertifikaten (nur Namen und Tage, keine Kryptografie). Zeit ist eine Zahl in Tagen:

Der letzte Fall ist ein häufiger Betriebsfehler: Der Server schickt das Zwischenzertifikat nicht mit, die Kette endet an einem Aussteller, der nicht im Vertrauensspeicher steht, also kommt “unbekannter Aussteller”.

Drei Begriffe, die dazugehören:

  • mTLS (mutual TLS): Auch der Client zeigt ein Zertifikat, und der Server prüft es. So weisen sich Services untereinander aus, ohne Passwörter.
  • Rotation: Zertifikate laufen ab (das Server-Zertifikat im Beispiel oben gilt 90 Tage). Wer sie von Hand tauscht, vergisst es irgendwann, und der Ausfall passiert genau am Ablauftag. Darum automatisch erneuern und vor dem Ablauf alarmieren.
  • Was TLS nicht verschlüsselt: die IP-Adresse des Servers und (je nach Aufbau) der angefragte Name im Handshake sind für das Netzwerk sichtbar. Verschlüsselt sind Pfad, Header und Inhalt. Und TLS schützt nur den Transport: Am Ende der Verbindung (Server, Proxy) liegen die Daten wieder im Klartext vor.

Ein Beispiel für die Rotation: Drei Zertifikate, Zeit in Tagen, jetzt ist Tag 30. Die Restlaufzeit ist bis - jetzt, und ein Alarm soll kommen, wenn sie höchstens 14 Tage beträgt:

db.intern hat noch 10 Tage und löst den Alarm aus, die anderen haben 60 und 370. Ein Zertifikat mit Restlaufzeit 0 oder weniger ist schon abgelaufen, das ist ein anderer, schlimmerer Zustand als “bald”.

Übung 1: Rotations-Alarm schreiben

Schreibe rotation_alarm(zertifikate, jetzt, warnfrist). Jedes Zertifikat ist ein Dict mit subject, von, bis (Tage). Die Funktion gibt ein Tupel (abgelaufen, bald) aus zwei Listen von Namen zurück.

  • abgelaufen: bis <= jetzt (ein Zertifikat gilt, solange von <= jetzt < bis ist).
  • bald: noch gültig (von <= jetzt < bis) und bis - jetzt <= warnfrist.
  • Zertifikate, die erst in der Zukunft gelten (von > jetzt), tauchen in keiner Liste auf.
  • Beide Listen sind nach bis aufsteigend sortiert, bei gleichem bis nach Namen. Die Eingabeliste darf nicht verändert werden.

Beispiel: Mit den drei Zertifikaten oben, jetzt=30 und warnfrist=14 ergibt sich ([], ["db.intern"]).

Sortiere zuerst (ohne die Eingabe zu verändern), dann sortiere jedes Zertifikat in genau einen der drei Zustände: abgelaufen, bald, nichts. Welche Grenzen sind inklusive, welche nicht?

def rotation_alarm(zertifikate, jetzt, warnfrist):
    abgelaufen, bald = [], []
    for z in sorted(zertifikate, key=lambda z: (z["bis"], z["subject"])):
        if z["bis"] <= jetzt:
            abgelaufen.append(z["subject"])
        elif z["von"] <= jetzt and z["bis"] - jetzt <= warnfrist:
            bald.append(z["subject"])
    return abgelaufen, bald

rotation_alarm

Abgelaufen ist schon bei bis == jetzt (die Gültigkeit endet vor bis). Die Warnung gilt inklusive der Grenze (<= warnfrist), aber nur für Zertifikate, die schon gelten und noch nicht abgelaufen sind. elif sorgt dafür, dass kein Zertifikat in beiden Listen steht.

Konzept 2: HTTP-Caching

Die schnellste Anfrage ist die, die gar nicht gesendet wird. Ein Cache speichert eine Antwort und beantwortet spätere Anfragen selbst. Es gibt ihn an mehreren Stellen: im Browser, in einem CDN (Content Delivery Network, Kopien nah am Nutzer), in einem Reverse Proxy vor deinem Server. Der Server steuert sie mit dem Header Cache-Control:

Direktive Bedeutung
max-age=60 60 Sekunden lang frisch (fresh): ohne Rückfrage verwenden
no-cache darf gespeichert werden, aber vor jeder Verwendung beim Server rückfragen (revalidieren)
no-store gar nicht speichern
stale-while-revalidate=30 bis 30 Sekunden nach Ablauf die veraltete Antwort noch ausliefern und im Hintergrund neu prüfen

Ist die Antwort veraltet (stale), wird sie nicht einfach verworfen. Wenn der Server einen ETag (Versionskennzeichen der Antwort, z. B. "v7") mitgeschickt hat, fragt der Cache nach: GET mit If-None-Match: "v7". Ist die Antwort unverändert, antwortet der Server mit 304 Not Modified und ohne Body, und der Cache darf seine Kopie weiter verwenden (Revalidierung). Sonst kommt 200 mit der neuen Antwort.

Ein Beispiel für max-age=60, gespeichert zum Zeitpunkt 0 (Sekunden):

Bis 59 ist die Antwort frisch, ab 60 nicht mehr. Dann gibt es zwei Wege: mit ETag revalidieren (billig, bei 304 nur Header übertragen) oder ohne ETag neu laden.

Invalidierung (cache invalidation) ist der schwierige Teil: Wie kommt eine geänderte Datei zu allen Caches, die sie noch haben? Zwei Strategien: (1) kurze Lebensdauer plus Revalidierung, (2) versionierte URLs (app.3f9a1c.js, der Dateiname enthält einen Hash des Inhalts), die man mit max-age von einem Jahr cachen kann, weil sich bei Änderung der Name ändert. Die HTML-Seite, die auf die Dateien zeigt, bekommt dagegen no-cache oder eine kurze max-age.

Zwei Begriffe aus der Praxis, nur zum Einordnen:

  • Cache Stampede (Thundering Herd): Ein beliebter Eintrag läuft ab, und alle Anfragen, die in dieser Zeit eintreffen, merken “nicht da” und fragen gleichzeitig den Ursprungsserver. Bei 1000 Anfragen pro Sekunde und 2 Sekunden Neuberechnung sind das 2000 Anfragen auf die teure Berechnung statt einer. Gegenmittel: Request Coalescing (nur der erste fragt, die anderen warten auf dessen Ergebnis) und Jitter (zufällig gestreute Ablaufzeiten, damit nicht alles gleichzeitig abläuft).
  • Write-through und Write-back beim Schreiben in einen Cache: Write-through schreibt synchron in Cache und Speicher dahinter (sicher, langsamer). Write-back schreibt zunächst nur in den Cache und später gesammelt weiter (schnell, aber bei Absturz gehen Änderungen verloren).

Übung 2: Cache-Entscheidung schreiben

Schreibe entscheide(antwort, jetzt) mit antwort = {"cache_control": str oder None, "etag": str oder None, "gespeichert_um": int}. Sie gibt einen von vier Strings zurück:

  • "frisch": ohne Rückfrage verwenden.
  • "stale": veraltete Kopie ausliefern und im Hintergrund prüfen (nur durch stale-while-revalidate).
  • "revalidieren": beim Server mit If-None-Match nachfragen (nur möglich, wenn ein ETag da ist).
  • "neu laden": komplett neu holen.

Regeln, in dieser Reihenfolge: (1) no-store ergibt "neu laden". (2) no-cache ergibt "revalidieren" mit ETag, sonst "neu laden". (3) Alter ist jetzt - gespeichert_um. Ist es kleiner als max-age, dann "frisch". Fehlt max-age, gilt 0. (4) Ist es kleiner als max-age + stale-while-revalidate, dann "stale". (5) Sonst "revalidieren" mit ETag, "neu laden" ohne. Die Direktiven stehen kommagetrennt, in beliebiger Reihenfolge und Groß-/Kleinschreibung, mit beliebigen Leerzeichen ("Public , Max-Age=60"). Ist cache_control gleich None, gibt es keine Direktiven.

Dazu status_bei_revalidierung(if_none_match, aktueller_etag): gibt 304 zurück, wenn der Client einen ETag mitschickt und er dem aktuellen entspricht, sonst 200.

Beispiel: {"cache_control": "max-age=60", "etag": '"v1"', "gespeichert_um": 0} bei jetzt=30 ergibt "frisch", bei jetzt=60 "revalidieren".

Zerlege cache_control zuerst in ein Dict aus Direktiven und Zahlen, dann prüfe die Regeln der Reihe nach. Was darf no-store überstimmen, und was passiert genau an der Grenze alter == max-age?

def entscheide(antwort, jetzt):
    direktiven = {}
    for teil in (antwort["cache_control"] or "").split(","):
        teil = teil.strip().lower()
        if not teil:
            continue
        name, _, wert = teil.partition("=")
        direktiven[name.strip()] = wert.strip()
    ersatz = "revalidieren" if antwort["etag"] else "neu laden"
    if "no-store" in direktiven:
        return "neu laden"
    if "no-cache" in direktiven:
        return ersatz
    alter = jetzt - antwort["gespeichert_um"]
    max_age = int(direktiven.get("max-age") or 0)
    swr = int(direktiven.get("stale-while-revalidate") or 0)
    if alter < max_age:
        return "frisch"
    if alter < max_age + swr:
        return "stale"
    return ersatz

def status_bei_revalidierung(if_none_match, aktueller_etag):
    if if_none_match is not None and if_none_match == aktueller_etag:
        return 304
    return 200

(entscheide, status_bei_revalidierung)

no-store und no-cache stehen vor dem Altersvergleich, sie gelten auch dann, wenn zusätzlich max-age da ist. An der Grenze (alter == max-age) ist die Antwort nicht mehr frisch (<, nicht <=).

Konzept 3: Proxy und Load Balancer

Ein Forward Proxy steht auf der Seite der Clients und schickt deren Anfragen ins Internet (Firmennetz, Filter). Ein Reverse Proxy steht auf der Seite der Server: Der Client glaubt, mit dem Reverse Proxy zu sprechen, der leitet an die eigentlichen Server weiter. Typische Aufgaben: TLS beenden, cachen, komprimieren, Anfragen verteilen. Ein Load Balancer (Lastverteiler) ist ein Reverse Proxy, dessen Hauptaufgabe das Verteilen auf mehrere Server ist, mit Health Checks (kaputte Server aus der Rotation nehmen).

Es gibt ihn auf zwei Ebenen:

  • L4 (Transportschicht): sieht nur IP-Adressen, Ports und TCP-Verbindungen. Er verteilt Verbindungen, ohne HTTP zu lesen. Schnell, einfach, kann aber nicht nach Pfad oder Header entscheiden.
  • L7 (Anwendungsschicht): versteht HTTP. Er kann TLS beenden, nach Pfad, Host oder Cookie weiterleiten, Header ergänzen und cachen. Kostet mehr Rechenzeit pro Anfrage.

Dazu kommt die Verteilstrategie: Round Robin gibt der Reihe nach jedem Server die nächste Anfrage. Least Connections gibt sie dem Server mit den wenigsten offenen Verbindungen. Wenn die Anfragen ähnlich lang dauern, ist Round Robin einfach und gut. Wenn die Dauer stark schwankt, nicht. Ein Beispiel: 3 Server, jeder bearbeitet eine Anfrage nach der anderen, pro Tick kommt eine neue Anfrage, die Dauern wiederholen sich im Muster 9, 1, 1 Ticks:

Bei Round Robin bekommt Server 0 jede lange Anfrage, die längste Wartezeit steigt auf 30 Ticks, im Schnitt 5.0. Bei Least Connections weicht die nächste Anfrage dem belegten Server aus: längste Wartezeit 9, Schnitt 2.89. Das Muster ist gebaut, damit der Effekt deutlich wird, aber es zeigt die Schwäche: Round Robin kennt die Last nicht.

Übung 3: Drei Entscheidungen

Entscheide drei kleine Fälle. Trage ein Tupel mit drei Buchstaben ein: (Fall 1, Fall 2, Fall 3).

Fall 1. Eine Web-App läuft auf zwei Server-Gruppen: Pfade unter /api/ auf Gruppe 1, alles andere auf Gruppe 2. Beide sollen unter einer Domain und einer IP erreichbar sein. Das TLS-Zertifikat soll an einer Stelle liegen.

  • A Ein L4 Load Balancer, der TCP-Verbindungen nach Quell-IP auf beide Gruppen aufteilt.
  • B Ein L7 Load Balancer, der TLS beendet und die Anfragen nach Pfad weiterleitet.
  • C DNS mit zwei A-Records, damit die Clients abwechselnd Gruppe 1 oder 2 bekommen.
  • D Zwei Ports auf einer IP, Port 8443 für Gruppe 1 und Port 9443 für Gruppe 2.

Fall 2. Vier gleiche Server hinter einem Load Balancer. Fast alle Anfragen dauern 20 Millisekunden, aber gelegentlich startet jemand einen Export, der 20 Sekunden dauert. Mit der bisherigen Verteilung hängen einzelne Server fest, andere langweilen sich.

  • A Round Robin beibehalten und zur Entlastung einen fünften Server dazustellen, der mitverteilt.
  • B Zufällige Auswahl, weil sich die langen Anfragen dann statistisch gleichmäßig verteilen.
  • C Die Client-IP hashen, damit jeder Nutzer immer auf demselben Server landet.
  • D Least Connections, damit neue Anfragen dem am wenigsten belegten Server zufallen.

Fall 3. Zwischen 40 internen Services läuft mTLS. Die Zertifikate gelten 365 Tage und werden von Hand getauscht. Letztes Jahr gab es genau am Ablauftag einen Ausfall, weil niemand daran gedacht hatte.

  • A Zertifikate automatisch erneuern lassen und bei einem Fehlschlag alarmieren.
  • B Die Gültigkeit auf zehn Jahre setzen, damit der Ablauf nur noch selten vorkommt.
  • C In den Clients die Prüfung des Ablaufdatums abschalten, damit abgelaufene Zertifikate nicht stören.
  • D Den Termin im Kalender mit zwei Personen besetzen, damit sich einer sicher darum kümmert.

Fall 1: Auf welcher Schicht steht die Information, nach der verteilt werden soll? Fall 2: Welche Größe kennt die Strategie, welche nicht? Fall 3: Was lässt sich auf Dauer verlässlich machen, ohne dass jemand daran denken muss?

antwort = ("B", "D", "A")
antwort

Fall 1, B. Der Pfad steht im HTTP und ist nur auf L7 sichtbar. Ein L7 Load Balancer kann TLS beenden (ein Zertifikat an einer Stelle) und nach Pfad verteilen. Ein L4 (A) sieht keinen Pfad, DNS (C) kennt keine Pfade und verteilt auf Verdacht, getrennte Ports (D) zwingen die Clients, Ports zu kennen, und zerreißen die Eine-Domain-Anforderung.

Fall 2, D. Least Connections berücksichtigt, wie viel ein Server gerade tut, und weicht belegten Servern aus. Round Robin (A) schickt auch mit fünf Servern jede fünfte Anfrage an einen Server, der gerade exportiert. Zufall (B) kann dieselbe Ballung erzeugen. Hashing der Client-IP (C) ist für Sticky Sessions gedacht und verteilt die Last ebenfalls nicht nach Belegung.

Fall 3, A. Ein Ablauf ist ein vorhersehbares Ereignis, also automatisierst du ihn: Kurzlebige Zertifikate werden von einem Mechanismus erneuert, ein Alarm meldet, wenn die Erneuerung scheitert. Zehn Jahre (B) heißen, dass der Tausch noch seltener geübt wird und ein gestohlener Schlüssel sehr lange gültig bleibt. Die Prüfung abzuschalten (C) entfernt den Sinn von Zertifikaten. Zwei Personen im Kalender (D) bleiben ein manueller Vorgang, der vergessen werden kann.

Falle

  1. no-cache für “nicht cachen” halten. Das ist no-store. no-cache heißt: speichern, aber vor jeder Verwendung rückfragen.
  2. Zwischenzertifikat vergessen. Der Server schickt es nicht mit, und nur manche Clients melden einen Fehler.
  3. Zertifikate von Hand tauschen. Der Ablauf ist vorhersehbar, und genau am Ablauftag fällt es jemandem auf. Automatisch erneuern, vor dem Ablauf alarmieren.
  4. Die HTML-Seite mit langer max-age ausliefern. Dann zeigt sie noch lange auf alte Dateien. Lang cachen darfst du nur versionierte URLs.
  5. Round Robin bei stark schwankender Anfragedauer. Er kennt die Last nicht und legt lange Anfragen immer wieder auf denselben Server.

Merksatz

Ein Zertifikat ist nur so gut wie seine lückenlose Kette und sein Ablaufdatum, und die schnellste Anfrage ist die, die ein Cache beantwortet: no-store speichert nichts, no-cache fragt vor jeder Verwendung zurück, und versionierte URLs machen lange Lebensdauern sicher.

Prüfstein

Eine geänderte JavaScript-Datei soll bei allen Nutzern ankommen, obwohl Browser und CDN sie cachen. Wie lieferst du Datei und HTML aus, welche Header setzt du, und was kostet die Revalidierung gegenüber einem Neuladen?


Quelle: quellen/konzeptuebersicht-software-grundlagen.md, Abschnitt “4. Betriebssystem und Netzwerk” (TLS und Zertifikate, Caching, Proxy und Load Balancer); quellen/konzeptuebersicht-software-fortgeschritten.docx, Schicht 4 “Betriebssystem, Netzwerk und Performance” (TLS 1.3, mTLS, Zertifikatsketten und Rotation; Load Balancer L4 gegen L7; Caching vertieft mit Write-through, Write-back, Cache Stampede und Invalidierungsstrategien).

Über die Quelle hinaus (allgemeines Fachwissen): die Regeln der Zertifikatsprüfung und die Zwischenzertifikat-Falle, die Rotations-Alarmregel (Restlaufzeit und Warnfrist), die Erklärung der Direktiven max-age, no-cache, no-store und stale-while-revalidate, ETag und 304, Request Coalescing und Jitter, die Beschreibung von Forward und Reverse Proxy, Round Robin und Least Connections. Alle Zahlen im Text stammen aus dem Ausführen des Codes dieser Lektion, nicht aus Messungen realer Netze.