Grundlagen moderner Websysteme   Vor kurzem aktualisiert!


Wer eine Website im Browser öffnet, sieht zunächst nur das Ergebnis: HTML, Bilder, Formulare oder eine komplette Webanwendung. Hinter einer modernen Website können jedoch zahlreiche unterschiedliche Komponenten arbeiten.

Web-Tools Übersicht (KI generiertes Bild)
Web-Tools Übersicht (KI generiertes Bild)

Apache, nginx, PHP, Node.js, Django, Tomcat, PostgreSQL oder Redis übernehmen dabei ganz unterschiedliche Aufgaben. Um moderne Webarchitekturen verstehen zu können, lohnt sich deshalb zunächst ein Blick auf die grundlegenden Bausteine eines Websystems.

Was passiert bei einem Webrequest?

Am Anfang steht der Client – meistens ein Webbrowser. Wird beispielsweise eine URL wie

https://shop.example.de/products?id=42

aufgerufen, enthält sie bereits mehrere wichtige Informationen:

https://shop.example.de:443/products?id=42
  │          │          │      │       │
  │          │          │      │       └─ Query
  │          │          │      └───────── Pfad
  │          │          └──────────────── Port
  │          └─────────────────────────── Hostname
  └────────────────────────────────────── Protokoll

Bevor der eigentliche HTTP-Request übertragen werden kann, muss zunächst geklärt werden, unter welcher IP-Adresse der angegebene Host erreichbar ist. Diese Aufgabe übernimmt das Domain Name System (DNS).

Vereinfacht entsteht damit folgende Kette:

Hostname
   │
   ▼
  DNS
   │
   ▼
IP-Adresse
   │
   ▼
 Server

Anschließend baut der Client eine Verbindung zum Server auf und sendet einen HTTP-Request.

Bei HTTPS kommt zusätzlich TLS zum Einsatz. Dadurch wird die Verbindung verschlüsselt und der Server kann sich gegenüber dem Client mit einem Zertifikat authentifizieren.

Der Webserver

Eine der zentralen Komponenten klassischer Websysteme ist der Webserver. Bekannte Vertreter sind:

  • Apache HTTP Server
  • nginx
  • Caddy

Bei einer statischen Website kann der Webserver die angeforderten Dateien direkt ausliefern.

Browser
   │
   ▼
Webserver
   │
   ▼
HTML · CSS · JavaScript · Bilder

Fordert der Browser beispielsweise /logo.png an, kann der Webserver diese URL unmittelbar einer Datei auf dem Server zuordnen.

GET /logo.png
      │
      ▼
Webserver
      │
      ▼
/var/www/site/logo.png

Für eine solche Website wird keine serverseitige Programmiersprache benötigt.

Dynamische Webanwendungen

Bei einer dynamischen Webanwendung existiert die vollständige Antwort dagegen häufig noch nicht als Datei. Sie wird erst während des Requests erzeugt.

Ein typischer Ablauf sieht beispielsweise so aus:

Browser ──► Webserver ──► Anwendung ──► Datenbank

Die Anwendung verarbeitet den Request, greift gegebenenfalls auf Daten zu und erzeugt daraus beispielsweise HTML oder JSON. Die Antwort gelangt anschließend über den Webserver zurück zum Client.

Bekannte Beispiele für dynamische Webanwendungen sind WordPress, TYPO3, Nextcloud und Django-Anwendungen.

Der klassische LAMP-Stack

Eine der bekanntesten Kombinationen für dynamische Websites ist der LAMP-Stack:

Linux
  │
Apache
  │
MySQL / MariaDB
  │
PHP

LAMP steht ursprünglich für Linux, Apache, MySQL und PHP.

Dieses Modell hat insbesondere die Entwicklung klassischer PHP-Anwendungen geprägt. Anwendungen wie WordPress wurden über viele Jahre typischerweise mit Apache, PHP und MySQL beziehungsweise MariaDB betrieben.

Dabei konnte Apache sehr viele Aufgaben gleichzeitig übernehmen: statische Dateien ausliefern, TLS terminieren, PHP ausführen beziehungsweise anbinden, URLs umschreiben und mehrere Websites auf demselben Server bereitstellen.

PHP-FPM trennt Webserver und PHP

Heute wird PHP häufig nicht mehr direkt innerhalb des Webservers ausgeführt. Stattdessen kommt PHP-FPM zum Einsatz.

Browser
   │
   ▼
Apache / nginx
   │
   │ FastCGI
   ▼
PHP-FPM
   │
   ▼
PHP-Anwendung

Der Webserver nimmt weiterhin HTTP-Requests entgegen. Muss PHP-Code ausgeführt werden, übergibt er den Request über FastCGI an PHP-FPM.

Damit sind Webserver und PHP-Laufzeit voneinander getrennt.

Dieses Prinzip ist für moderne Architekturen wichtig: Eine einzelne Software muss nicht mehr sämtliche Aufgaben übernehmen.

Python und Django

Das gleiche Grundprinzip findet man auch in anderen Ökosystemen.

Eine Django-Anwendung könnte beispielsweise folgendermaßen betrieben werden:

Browser
   │
   ▼
nginx / Apache
   │
   ▼
Gunicorn
   │
   ▼
Django
   │
   ▼
PostgreSQL

Django selbst ist dabei kein Webserver, sondern ein Webframework für Python.

Gunicorn stellt die Verbindung zwischen dem HTTP- beziehungsweise Proxy-Layer und der Python-Anwendung her.

Node.js

Bei Node.js sieht die Situation etwas anders aus. Eine Node.js-Anwendung kann selbst einen HTTP-Port öffnen.

Browser
   │
   ▼
nginx / Caddy
   │
   ▼
Node.js :3000
   │
   ▼
Anwendung

Technisch könnte der Browser auch direkt mit Node.js kommunizieren. In produktiven Umgebungen befindet sich jedoch häufig ein Webserver oder Reverse Proxy davor.

Dieser kann beispielsweise TLS, Routing und die Verteilung von Requests übernehmen.

Java und Tomcat

Auch Java-Webanwendungen verwenden häufig eine eigene Laufzeitumgebung beziehungsweise einen Application Server.

Browser
   │
   ▼
nginx / Apache
   │
   ▼
Tomcat
   │
   ▼
Java-Anwendung

Ein Beispiel hierfür ist XWiki, das typischerweise in einer Java-Servlet-Umgebung wie Tomcat betrieben wird.

Damit wird deutlich: Nicht jede Webanwendung folgt dem klassischen LAMP-Modell.

Unterschiedliche Anwendungen – unterschiedliche Stacks

Je nach Anwendung kann der technische Stack erheblich variieren.

AnwendungRuntimetypische Bereitstellung
WordPressPHPApache oder nginx + PHP
DjangoPythonGunicorn + Reverse Proxy
Node.js-AnwendungJavaScriptNode.js + Reverse Proxy
XWikiJavaTomcat + Reverse Proxy
statische WebsitekeineApache, nginx oder Caddy

Es gibt daher nicht den einen Webstack.

Entscheidend ist vielmehr, welche Komponenten eine Anwendung benötigt und wie diese miteinander verbunden werden.

Was ist ein Framework?

Zusätzlich muss zwischen Programmiersprache, Laufzeit und Framework unterschieden werden.

Ein Framework stellt Strukturen und Bibliotheken für die Entwicklung einer Anwendung bereit.

Programmiersprache
       │
       ▼
    Framework
       │
       ▼
  Webanwendung

Beispiele sind:

SpracheFrameworks
PythonDjango, Flask, FastAPI
PHPSymfony, Laravel
JavaScriptExpress, NestJS, Next.js

Ein Framework ersetzt also nicht automatisch den Webserver oder die Laufzeitumgebung.

Reverse Proxies

Mit zunehmender Trennung der Komponenten gewinnt der Reverse Proxy an Bedeutung.

Angenommen, auf einem Server laufen mehrere Anwendungen:

                    ┌────► Node.js :3000
                    │
Internet ─► nginx ──┼────► Django  :8000
                    │
                    └────► Tomcat  :8080

Von außen müssen die internen Ports nicht bekannt sein.

Der Reverse Proxy nimmt die Requests entgegen und entscheidet anhand von Hostnamen oder Pfaden, an welches Backend sie weitergeleitet werden.

Dadurch entsteht eine zentrale Eingangsschicht für mehrere unterschiedliche Anwendungen.

Web Tools (KI generiertes Bild)
Web Tools (KI generiertes Bild)

Welche Informationen benötigt ein Webserver?

Unabhängig vom eingesetzten Produkt müssen bei der Konfiguration immer wieder ähnliche Fragen beantwortet werden:

  • Auf welcher IP-Adresse und welchem Port soll ein Dienst lauschen?
  • Für welchen Hostnamen ist er zuständig?
  • Sollen Dateien ausgeliefert oder Requests an ein Backend weitergeleitet werden?
  • Soll HTTP oder HTTPS verwendet werden?
  • Welche Header oder Routing-Regeln gelten?
  • Wo werden Zugriffe und Fehler protokolliert?

Die konkrete Syntax unterscheidet sich zwischen Apache, nginx oder Caddy. Die zugrunde liegenden Fragen bleiben jedoch ähnlich.

Beispiel: Apache VirtualHost unter Debian

Unter Debian wird die Apache-Konfiguration typischerweise unter /etc/apache2/ verwaltet.

Wichtige Verzeichnisse sind unter anderem:

/etc/apache2/
   ├── apache2.conf
   ├── ports.conf
   ├── sites-available/
   ├── sites-enabled/
   ├── mods-available/
   ├── mods-enabled/
   ├── conf-available/
   └── conf-enabled/

Eine einfache Website könnte beispielsweise unter

/var/www/example

liegen.

Dazu wird ein VirtualHost definiert:

<VirtualHost *:80>
    ServerName www.example.de
    ServerAlias example.de

    DocumentRoot /var/www/example

    ErrorLog ${APACHE_LOG_DIR}/example-error.log
    CustomLog ${APACHE_LOG_DIR}/example-access.log combined
</VirtualHost>

Die wesentlichen Direktiven lassen sich direkt den grundlegenden Konfigurationsfragen zuordnen:

Apache-DirektiveBedeutung
<VirtualHost *:80>auf welchem Port Apache lauscht
ServerNameprimärer Hostname
ServerAliaszusätzliche Hostnamen
DocumentRootVerzeichnis der auszuliefernden Dateien

Unter Debian kann die Konfiguration anschließend beispielsweise mit a2ensite aktiviert werden.

sudo a2ensite example.conf

Vor dem Neuladen sollte die Konfiguration geprüft werden:

sudo apache2ctl configtest

Erst wenn die Prüfung erfolgreich ist, wird Apache neu geladen:

sudo systemctl reload apache2

Vom DocumentRoot zum Backend

Nicht jede Anwendung besitzt jedoch einen klassischen DocumentRoot.

Eine Node.js-Anwendung könnte beispielsweise auf Port 3000 laufen. Apache kann Requests an diesen Dienst weiterleiten:

www.example.de ──► Apache ──► Node.js :3000
                      Proxy

Der Webserver liefert die Anwendung in diesem Fall nicht selbst aus. Er fungiert als Reverse Proxy.

Genau an dieser Stelle beginnt der Übergang von der klassischen Webserver-Konfiguration zur Webarchitektur.

Von Komponenten zur Architektur

Die grundlegenden Bausteine lassen sich vereinfacht zusammenfassen:

DNS / TLS
    │
    ▼
Webserver ──► Runtime ──► Daten

Dabei können unterschiedliche Technologien eingesetzt werden:

Webserver: Apache, nginx, Caddy

Runtimes: PHP-FPM, Node.js, Python, Tomcat

Daten und Dienste: MariaDB, PostgreSQL, Redis, Storage

Ein Websystem entsteht jedoch nicht allein dadurch, dass diese Komponenten vorhanden sind.

Entscheidend ist ihr Zusammenspiel.

Damit stellt sich die nächste Frage:

Wie werden diese Komponenten zu einer wartbaren, sicheren und flexibel betreibbaren Webarchitektur zusammengesetzt?