Früher war WordPress für mich gesetzt. Ein Server, MySQL, PHP, ein Theme, Plugins, fertig. Irgendwann wurde daraus: Zwei Server, ein Reverse Proxy, Redis-Cache, ein Page-Builder, Security-Plugins, Monitoring, ständige Updates, und eine Datenbank, die regelmäßig gecrasht ist.
Irgendwann hab ich die Bremse gezogen.
Was mich an großen Blog-Systemen stört
1. WordPress – die wartungsintensive Plattform
| Faktor | Aufwand |
|---|---|
| Updates | Core + Plugins + Themes, oft wöchentlich |
| Sicherheit | Ständig unter Beschuss, hardening nötig |
| PHP + MySQL | Zwei Dienste, die gecared werden müssen |
| Backup | Datenbank + Dateien, kein einfaches cp |
| Geschwindigkeit | Ohne Caching langsam, mit Caching komplex |
WordPress selbst ist nicht das Problem. Aber WordPress bedeutet automatisch drei Angriffsflächen: Core, Plugins, Themes. Jede Komponente muss aktuell sein, kompatibel bleiben, und darf sich nicht in die Quere kommen. Das ist kein Bloggen mehr, das ist Systemadministration.
2. Ghost – schlank, aber nicht schlank genug
Ghost ist moderner, hat weniger Angriffsfläche, und Node.js ist performant. Aber auch Ghost bedeutet: ein Server-Prozess, der läuft, eine Datenbank (SQLite oder MySQL), ein Reverse Proxy davor, und regelmäßige Updates. Plus: Ghost möchte in einer bestimmten Version laufen, hat Abhängigkeiten, und eine Migration ist aufwändiger als eine Markdown-Datei zu kopieren.
3. Der gemeinsame Nenner
Alle diese Systeme haben:
- Eine Datenbank – Single Point of Failure, Backup ≠
cp - Einen laufenden Prozess – muss überwacht, gemonitort, gepatched werden
- Ein Admin-Panel – Angriffsfläche, die man im Idealfall nicht braucht
- Abhängigkeiten – PHP, Node, MySQL, Redis – alles Dinge, die kaputtgehen können
Diese Seite (frischux.de) – wie sie wirklich läuft
Die Dateistruktur
/var/www/html/
├── index.html ← statisch
├── blog.html ← vorgerendert (build-static.py)
├── post.html ← dynamischer Fallback (JS)
├── impressum.html ← statisch
├── statistik.html ← täglich aus access.log generiert
├── artikel/ ← vorgerenderte Beitragsseiten
│ ├── paperless-ngx-dokumentenmanagement.html
│ └── *.html ← alle Beiträge
├── css/style.css ← einmal geschrieben, nie wieder angefasst
├── js/blog.js ← ~170 Zeilen, eine Abhängigkeit (marked.js)
├── posts/
│ ├── posts.json ← Metadaten, per Hand gepflegt
│ ├── *.md ← Beiträge in reinem Markdown
│ └── feed.xml ← Atom-Feed, per Hand aktualisiert
├── images/posts/ ← generierte KI-Bilder, lokal gecached
├── scripts/
│ ├── build-static.py ← generiert artikel/*.html + blog.html + sitemap.xml
│ ├── download-images.py ← lädt fehlende KI-Bilder nach
│ └── stats.py ← baut statistik.html aus nginx-log
├── robots.txt
└── sitemap.xml ← automatisch generiert
Keine Datenbank. Kein PHP. Kein laufender App-Server.
Der Hybrid-Ansatz: statisch + dynamisch
Dieses Blog fährt eine Mischstrategie:
- Statisch (primär): Ein Python-Script (
scripts/build-static.py) rendert jeden.md-Beitrag in eine eigene HTML-Datei unterartikel/. Die Blog-Übersicht (blog.html) wird ebenfalls vorgerendert. Die Sitemap wird automatisch aktualisiert. Der statische Build läuft alle 20 Minuten per Cron – so sind händische Änderungen an Markdown-Dateien schnell sichtbar.
- Dynamisch (Fallback): Wer
post.html?slug=artikel-nameaufruft (z. B. über ein Lesezeichen), bekommt denselben Inhalt – gerendert viamarked.jsim Browser. Das ermöglicht einen fließenden Übergang für alte URLs.
- RSS/Atom: Der Feed in
posts/feed.xmlwird manuell gepflegt – ein Trade-off, den ich für die Einfachheit in Kauf nehme.
Wie ein Beitrag ausgeliefert wird (statischer Pfad)
- Der Browser ruft
artikel/paperless-ngx-dokumentenmanagement.htmlauf - nginx liefert die Datei direkt aus – kein PHP, kein JS, kein Datenbank-Query
- Die Seite ist in unter 100 ms da
Wie ein Beitrag ausgeliefert wird (dynamischer Pfad)
- Der Browser lädt
blog.htmlundjs/blog.js blog.jsholt perfetch()dieposts/posts.json- Die Beitragsliste wird aus dem JSON gerendert
- Ein Klick auf einen Beitrag öffnet
post.html?slug=... post.htmllädt die.md-Datei und rendert sie viamarked.jsim Browser
Was automatisch passiert
Drei Cron-Jobs laufen im Hintergrund:
| Alle … | Script | Aufgabe |
|---|---|---|
| 5 Minuten | stats.py |
Neue Zugriffe aus access.log auswerten (Bots gefiltert) |
| 5 Minuten | download-images.py |
Fehlende KI-Artikelbilder nachladen |
| 20 Minuten | build-static.py |
Geänderte Markdowns in statische HTMLs rendern |
Was daran besser ist
Backup: CODEBLOCK1 Fertig. Kein MySQL-Dump, kein phpMyAdmin, kein "hab ich die Datenbank mitgesichert?".
Update eines Beitrags: CODEBLOCK2 Kein Login ins Admin-Panel, kein "wie hieß dieser eine Block-Editor-Shortcut noch?". Ein Editor, eine Datei, git commit, fertig.
Sicherheit: Es gibt keine Angriffsfläche. Kein PHP, keine Datenbank, kein Admin-Login. Die Seite exponiert nichts außer statischen Dateien – und einem nginx, der sie ausliefert.
Geschwindigkeit: Die statischen Seiten laden in ~50 ms. Kein PHP-FPM-Start, keine Datenbank-Connection, kein Rendering.
Was man verliert
Fairerweise: nicht jeder braucht das. Ein statischer Blog bedeutet:
- Kein WYSIWYG-Editor – ich schreibe in Vim/Markdown
- Kein Admin-Panel – kein Dashboard, keine Kommentare
- Kein CDN-Plugin – Cloudflare oder ein einfacher nginx reichen
- Keine User-Verwaltung – es gibt nur mich
- Kein automatischer RSS-Feed – der Feed ist manuelle Handarbeit
Für Leser ändert sich nichts. Für mich ändert sich alles: Weniger Arbeit, mehr Kontrolle.
Fazit
Ich will nicht mehr administrieren, um zu bloggen. Ich will schreiben, eine Datei speichern, und fertig.
Diese Seite läuft auf einem einfachen nginx, hat keine Updates, keine Datenbank, kein PHP, kein JavaScript-Framework. Der einzige Build-Schritt ist ein Python-Script, das alle 20 Minuten kurz läuft. Die Seite ist zehnmal so schnell wie mein altes WordPress-Setup und hat genau null Angriffsfläche für App-Level-Exploits.
Manchmal ist die beste Technik die, die man nicht sieht – weil sie einfach nicht da ist.
