Nach Jahren mit WordPress habe ich meinen Blog auf Hugo umgezogen. Der Auslöser war kein einzelnes Ereignis, sondern eine nüchterne Bestandsaufnahme: Ich wollte kein PHP mehr im Live-Betrieb, keine Datenbank, die ständig gepatcht werden muss, und vor allem ein System, in dem KI-Tools automatisiert Inhalte bearbeiten, übersetzen und pflegen können, ohne dass ich mir Sorgen um Angriffsflächen machen muss.

In diesem Beitrag erkläre ich zunächst, warum ich mich für Hugo statt WordPress entschieden habe, mit Fokus auf Sicherheit und KI-Automatisierung. Danach folgt die praktische Anleitung, wie ich Hugo unter Windows lokal eingerichtet habe, inklusive des Themes PaperMod, und wie die Seite per GitHub Actions auf den Server kommt.

Hugo vs. WordPress: Warum der Wechsel

WordPress betreibt einen großen Teil des Webs und ist gerade deshalb das bevorzugte Ziel automatisierter Angriffe. Die eigentliche Schwäche liegt weniger im WordPress-Kern als im Ökosystem darum herum. Laut dem Bericht State of WordPress Security in 2026 von Patchstack wurden 2025 im WordPress-Umfeld 11.334 neue Schwachstellen gefunden, 42 Prozent mehr als im Jahr davor. 91 Prozent davon steckten in Plugins, 9 Prozent in Themes und nur sechs im WordPress-Kern selbst. Für 46 Prozent der Lücken gab es zum Zeitpunkt der Veröffentlichung noch keinen Patch. Besonders kritisch ist die Geschwindigkeit: Schwerwiegende Lücken werden laut Patchstack im Mittel schon fünf Stunden nach ihrer Veröffentlichung zum ersten Mal ausgenutzt, etwa die Hälfte innerhalb von 24 Stunden. Wer nicht praktisch permanent aktualisiert, ist zu langsam.

Diese Anfälligkeit erklärt sich aus der Architektur. Eine WordPress-Seite läuft in der Regel mit vielen Plugins, und jedes davon ist eigenständiger Code von einem anderen Autor mit einem eigenen Update-Zyklus. Jedes Plugin ist eine zusätzliche Tür in die Installation, und jede Tür braucht ein eigenes Schloss, das dauerhaft gewartet werden muss. Hinzu kommt, dass auch Angreifer inzwischen KI einsetzen, um bekannte Schwachstellen schneller zu finden und auszunutzen.

Warum Hugo strukturell kaum angreifbar ist

Hugo verfolgt einen grundlegend anderen Ansatz. Es gibt keinen Datenbankserver, kein PHP und keine Admin-Oberfläche, die im Internet erreichbar ist. Hugo erzeugt aus Markdown-Dateien, Templates und Front Matter fertige statische HTML-Dateien, die anschließend nur noch von einem einfachen Webserver ausgeliefert werden. Auf dem Server läuft also kein Programmcode, in dem sich eine Schwachstelle ausnutzen ließe: keine SQL Injection, keine PHP Remote Code Execution, kein verwundbares Plugin-Ökosystem. Angreifbar ist im Wesentlichen nur noch der Webserver selbst, und den muss man ohnehin härten und aktuell halten, egal welches CMS dahinter steht.

Das bedeutet nicht, dass Hugo-Seiten unangreifbar sind. Aber die gesamte Kategorie von Angriffen, die WordPress so anfällig macht, entfällt, weil es die Angriffsfläche dafür gar nicht gibt.

Geschwindigkeit: Statt Rendering nur noch Ausliefern

Ein Punkt, der im Alltag sofort spürbar ist, ist die Geschwindigkeit. WordPress baut jede Seite bei jedem Aufruf neu zusammen: PHP wird ausgeführt, die Datenbank abgefragt, das Ergebnis gerendert und erst dann ausgeliefert. Bei jedem einzelnen Besucher, immer wieder. Caching-Plugins mildern das ab, sind aber im Grunde nur ein Pflaster über einer aufwendigen Architektur.

Hugo dreht das Prinzip um. Die Seiten werden einmal beim Build erzeugt, danach liefert der Server nur noch fertige HTML-Dateien aus, ohne Rendering, ohne Datenbankabfrage, ohne serverseitige Verarbeitung. Schneller lässt sich eine Website kaum ausliefern, und über ein CDN lässt sie sich ohne Aufwand weltweit verteilen. Hinzu kommt die Build-Geschwindigkeit selbst: Hugo ist in Go geschrieben und gehört zu den schnellsten Static Site Generatoren. Ein Blog mit einigen hundert Seiten ist in wenigen Sekunden gebaut. Für den lokalen Workflow bedeutet das: Ich speichere eine Änderung und sehe sie praktisch im selben Moment im Browser.

Der KI-Faktor: Markdown als Automatisierungsschnittstelle

Der dritte große Vorteil für mich liegt in der Ablage der Inhalte. WordPress speichert Beiträge in einer relationalen Datenbank, auf die eine KI nur über eine API oder ein Plugin zugreifen kann, mit allen damit verbundenen Risiken und Abhängigkeiten. Hugo-Inhalte liegen dagegen als einfache Markdown-Dateien im Dateisystem, versioniert mit Git.

Das ermöglicht eine ganz andere Art der Automatisierung: Eine KI kann Markdown-Dateien direkt lesen und bearbeiten, Beiträge in mehrere Sprachen übersetzen, Front Matter einheitlich anpassen oder SEO-Metadaten pflegen. Jede dieser Änderungen ist als Git-Commit nachvollziehbar, statt unsichtbar in einer Datenbank zu verschwinden. Änderungen lassen sich vor der Veröffentlichung per Pull Request prüfen und im Zweifel per Git-Revert rückgängig machen. Bei mir übersetzen zum Beispiel zwei kleine lokale Python-Skripte jeden Beitrag automatisch ins Englische und Spanische und legen die Übersetzung als index.en.md und index.es.md neben das Original. Für einen Workflow, in dem KI-Tools regelmäßig am Inhalt mitarbeiten, ist das eine deutlich robustere und transparentere Grundlage als ein CMS mit Datenbank im Hintergrund.

Die Nachteile von Hugo im Vergleich

Der Wechsel hat auch einen Preis. Hugo bringt keine Redaktionsoberfläche mit. Autoren ohne technisches Vorwissen brauchen ein zusätzliches Headless CMS oder Unterstützung beim Deployment. Dynamische Funktionen wie Kommentare, Suche oder Formulare lassen sich nicht serverseitig lösen, sondern nur über externe Dienste oder JavaScript nachrüsten. Die Suche in PaperMod funktioniert zum Beispiel komplett im Browser über einen JSON-Index, den Hugo beim Build erzeugt. Und zwischen einer Änderung und der Veröffentlichung auf dem Live-Server liegt immer ein Build-Schritt. Lokal fällt das nicht ins Gewicht: Der eingebaute Entwicklungsserver zeigt jede Änderung dank Live-Reload sofort im Browser an, oft schneller als die Vorschau im WordPress-Editor.

Für einen technisch versierten Ein-Personen-Blog mit Fokus auf Performance, Sicherheit und KI-gestützter Pflege überwiegen für mich klar die Vorteile. Für ein Redaktionsteam ohne Git-Kenntnisse wäre die Abwägung anders ausgefallen.

Voraussetzungen: Git und Hugo installieren

Bevor es losgeht, brauchst du zwei Werkzeuge auf deinem Windows-Rechner, der bei diesem Setup als Entwicklungs- und Testumgebung dient. Ich gehe davon aus, dass noch keines davon installiert ist.

1. Git für Windows

Git wird für die Versionsverwaltung und zum Einbinden des Themes als Submodule benötigt. Den offiziellen Installer lädst du hier herunter:

https://git-scm.com/download/win

Die heruntergeladene .exe ausführen und den Installations-Assistenten durchlaufen. Die Standardeinstellungen reichen aus. Achte nur darauf, dass Git zum System-PATH hinzugefügt wird, das ist in der Standardauswahl bereits der Fall.

2. Hugo (Extended Edition)

Hugo ist der eigentliche Static Site Generator. Ich verwende die Extended Edition. Sie enthält zusätzlich LibSass. Das ist eine Programmbibliothek, die Sass in CSS übersetzt. Sass ist eine Erweiterung von CSS, mit der sich Stylesheets kürzer und übersichtlicher schreiben lassen, etwa mit Variablen für Farben oder mit verschachtelten Regeln. Browser verstehen Sass nicht, deshalb muss Hugo es beim Build in normales CSS umwandeln. SCSS ist die heute übliche Schreibweise von Sass: Sie sieht aus wie CSS mit geschweiften Klammern und Semikolons, die Dateien enden auf .scss. PaperMod selbst kommt mit normalem CSS aus, viele andere Themes setzen die Extended Edition aber voraus. Wer sie von Anfang an nutzt, kann später das Theme wechseln, ohne Hugo neu zu installieren. Am Zusatz +extended in der Versionsausgabe erkennst du, ob die richtige Variante installiert ist.

Die aktuelle Version findest du auf der offiziellen Releases-Seite. Die Seite listet für jede Version mehrere Dutzend Dateien auf. Für einen normalen Windows-PC brauchst du die Datei nach dem Muster hugo_extended_<Version>_windows-amd64.zip, bei Version 0.167.0 also hugo_extended_0.167.0_windows-amd64.zip. Die Datei mit withdeploy im Namen enthält zusätzlich Funktionen zum Hochladen in Cloud-Speicher wie AWS S3 und wird hier nicht gebraucht. Die Dateien ohne extended im Namen sind die Standard Edition ohne LibSass:

https://github.com/gohugoio/hugo/releases

Die ZIP-Datei entpackst du in einen festen Ordner, zum Beispiel C:\Hugo\Bin. Damit der Befehl hugo in jeder Eingabeaufforderung funktioniert, musst du diesen Ordner zur PATH-Umgebungsvariable hinzufügen:

  1. Im Startmenü nach “Umgebungsvariablen” suchen und “Systemumgebungsvariablen bearbeiten” öffnen
  2. Auf “Umgebungsvariablen…” klicken
  3. Unter “Systemvariablen” die Variable Path auswählen und auf “Bearbeiten” klicken
  4. “Neu” klicken und den Pfad zum Hugo-Ordner eintragen, z.B. C:\Hugo\Bin
  5. Alle Fenster mit “OK” schließen

Alternative über einen Paketmanager: Schneller geht es über die Kommandozeile. Der Windows Package Manager winget ist bei aktuellen Windows-Versionen in der Regel vorinstalliert, andernfalls lässt er sich über den Microsoft Store als “App Installer” nachinstallieren:

winget install --id Git.Git -e --source winget
winget install --id Hugo.Hugo.Extended -e --source winget

Wer Chocolatey nutzt, installiert Hugo mit choco install hugo-extended. Nach jeder Installation ein neues Terminal öffnen, damit die geänderte PATH-Variable gilt.

Installation überprüfen

Danach kannst du in der Eingabeaufforderung prüfen, ob alles korrekt eingerichtet ist:

git --version
hugo version

Bei mir sah die Ausgabe so aus:

C:\Users\info>git --version
git version 2.54.0.windows.1
C:\Users\info>hugo version
hugo v0.163.2-19a5cec0b9618163bb519487382e861d29edf383+extended windows/amd64 BuildDate=2026-06-15T14:55:00Z VendorInfo=gohugoio

Go muss übrigens nicht installiert sein. Hugo ist ein fertig kompiliertes Programm, Go braucht man nur, wenn man Hugo selbst bauen oder Themes als Hugo Module statt als Git Submodule einbinden will.

Schritt 1: Repository und Projektstruktur anlegen

Ich habe das Git-Repository im Ordner C:\sources\aaron_de angelegt und die Hugo-Seite in den Unterordner hugo gepackt. So bleibt neben der Seite Platz für Hilfsdateien wie die Pipeline-Konfiguration unter .github:

cd C:\sources\aaron_de
git init
hugo new site hugo

Der Befehl hugo new site legt den kompletten Ordnerbaum an, den Hugo für ein Projekt benötigt: content, layouts, static, themes sowie die zentrale Konfigurationsdatei hugo.toml.

Schritt 2: Theme als Git Submodule einbinden

PaperMod wird nicht kopiert, sondern als Git Submodule eingebunden. Die Dateien des Themes landen dabei nicht in meinem Repository. Git speichert dort nur zwei Angaben: die Adresse des PaperMod-Repositories auf GitHub (in der Datei .gitmodules) und die Kennung des Commits, also den genauen Stand des Themes, den meine Seite verwendet. Die Befehle laufen im Hauptverzeichnis des Repositories, also dort, wo der Ordner .git liegt:

cd C:\sources\aaron_de
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git hugo/themes/hugo-PaperMod

Der Parameter --depth=1 sorgt dafür, dass nur der aktuelle Stand des Theme-Repositories geklont wird, ohne die komplette Commit-Historie. Das spart Zeit und Speicherplatz.

Stolperstein beim erneuten Auschecken: Wer das Repository später auf einem anderen Rechner klont oder neu auscheckt, findet den Ordner hugo/themes/hugo-PaperMod zunächst leer vor. Git lädt Submodule nicht automatisch mit. Hugo bricht dann beim Start mit einem Fehler zum fehlenden Theme ab. Abhilfe schafft dieser Befehl im Hauptverzeichnis des Repositories:

git submodule update --init --recursive

Alternativ lädst du das Theme gleich beim Klonen mit. Dazu gibst du die Adresse deines eigenen Repositories auf GitHub und den Zielordner an:

git clone --recurse-submodules https://github.com/<benutzer>/<repository>.git C:\sources\aaron_de

Für <benutzer> und <repository> setzt du deinen GitHub-Benutzernamen und den Namen deines Repositories ein. Die passende Adresse zeigt GitHub auf der Seite des Repositories unter dem grünen Button “Code”.

Schritt 3: Konfiguration anpassen

In der Datei hugo/hugo.toml wird festgelegt, welches Theme aktiv ist und welche Grundeinstellungen gelten. Der Wert bei theme muss genau dem Ordnernamen unter themes entsprechen:

baseURL = 'http://localhost:1313/'
locale = 'de-DE'
title = 'aaron.de'
theme = 'hugo-PaperMod'

[markup.goldmark.renderer]
  unsafe = true

Zur Einstellung unsafe = true: Hugo gibt HTML, das direkt im Markdown steht, standardmäßig nicht aus und ersetzt es durch einen Kommentar. Meine aus WordPress übernommenen Beiträge enthalten aber HTML-Schnipsel wie <br/>. Erst mit unsafe = true erscheinen sie auf der Seite. Der Name klingt gefährlicher, als es hier ist: Die Einstellung erlaubt HTML aus meinen eigenen Markdown-Dateien, nicht aus Eingaben fremder Besucher.

Schritt 4: Lokalen Server starten

Zum Schluss startest du den eingebauten Entwicklungsserver im Hugo-Ordner:

cd C:\sources\aaron_de\hugo
hugo server -D

Das Flag -D sorgt dafür, dass auch Beiträge mit draft: true angezeigt werden, was für lokale Tests praktisch ist. Die Konsole zeigt danach unter anderem an, wie viele Seiten gebaut wurden und wo der Server erreichbar ist:

Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

Solange der Server läuft, erscheinen Änderungen an Inhalten oder Konfiguration automatisch im Browser, ohne dass ein Neustart nötig ist.

Vom lokalen Test zum Live-Server: Meine Deployment-Pipeline

Mein Windows-Rechner dient ausschließlich als Testumgebung. Hier schreibe und prüfe ich Inhalte lokal mit hugo server -D, bevor irgendetwas live geht. Der Weg auf den Server läuft danach automatisch:

  1. Lokale Änderung: Inhalt oder Layout unter Windows anpassen und im Browser unter localhost:1313 prüfen
  2. Commit und Push nach GitHub: Sobald ein Beitrag fertig ist, wird er committed und in das GitHub-Repository gepusht
  3. CI/CD-Pipeline: Eine GitHub Actions Pipeline startet beim Push, baut die Seite mit Hugo und überträgt das Ergebnis auf den Zielserver
  4. Live-Server: Der Webserver liefert die neu gebauten statischen Dateien aus, ohne dass ich auf dem Server selbst etwas ausführen muss

Der Vorteil dieses Aufbaus: Auf dem Produktivserver landen nur fertig gebaute Dateien. Niemand bearbeitet dort direkt Inhalte, und Beiträge mit draft: true baut die Pipeline gar nicht erst mit. Damit übertrage ich das Prinzip von Staging und Produktion, das ich aus klassischen Softwareprojekten kenne, auf den Blog.

Die GitHub Actions Pipeline im Detail

Die Pipeline ist eine Workflow-Datei im Hauptverzeichnis des Repositories, nicht im Unterordner hugo. Bei mir liegt sie unter C:\sources\aaron_de\.github\workflows\deploy.yml. GitHub sucht Workflows nur im Ordner .github\workflows direkt im Hauptverzeichnis, eine Datei an anderer Stelle würde nie ausgeführt. Die Pipeline startet bei jedem Push auf den Branch main und lässt sich zusätzlich manuell über den Button “Run workflow” im Reiter Actions auf GitHub starten:

name: Deploy Hugo Site

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build-deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: hugo
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive
          fetch-depth: 0

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: 'latest'
          extended: true

      - name: Build with Production Settings
        env:
          HUGO_PARAMS_ENV: production
          HUGO_ENV: production
        run: |
          hugo --gc --minify --baseURL "https://example.com/"

      - name: Deploy to Server
        uses: easingthemes/ssh-deploy@main
        with:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          ARGS: "-rlgoDzvc -i --delete"
          SOURCE: "hugo/public/"
          REMOTE_HOST: ${{ secrets.SERVER_IP }}
          REMOTE_USER: "aaron"
          REMOTE_PORT: ${{ secrets.SERVER_PORT }}
          TARGET: "/var/www/aaron/aaron_de/"

Ein paar Details, die für diesen Ablauf wichtig sind:

  • working-directory: hugo: Die Hugo-Seite liegt nicht im Hauptverzeichnis des Repositories, sondern im Unterordner hugo. Deshalb wird dieser Ordner als Arbeitsverzeichnis für die Build-Schritte gesetzt. Der Checkout mit submodules: recursive sorgt dafür, dass auch das PaperMod-Theme mit ausgecheckt wird. Ohne diese Zeile hätte die Pipeline dasselbe Problem mit dem leeren Theme-Ordner wie ein frischer Klon auf dem eigenen Rechner.
  • hugo-version: 'latest' und extended: true: Die Pipeline verwendet immer die neueste Extended-Version von Hugo. Die lokal installierte Version dient nur der Vorschau und hat keinen Einfluss auf die Live-Seite. Deshalb halte ich sie mit winget upgrade Hugo.Hugo.Extended (oder choco upgrade hugo-extended) aktuell, damit ich lokal dasselbe sehe wie die Pipeline.
  • Produktions-Build: --gc entfernt nach dem Build nicht mehr benötigte Dateien aus dem Cache, --minify verkleinert HTML, CSS und JS. Der Befehl hugo baut ohne weitere Angaben in der Umgebung production, der lokale hugo server dagegen in der Umgebung development. PaperMod bindet nur in der Produktionsumgebung Analytics und Verifizierungs-Tags ein und erlaubt nur dort Suchmaschinen das Indexieren. Meine lokalen Testaufrufe landen deshalb nicht in der Statistik. Die Variablen HUGO_ENV und HUGO_PARAMS_ENV legen die Produktionsumgebung in der Pipeline zusätzlich ausdrücklich fest. HUGO_PARAMS_ENV überschreibt dabei den Parameter env = "development" aus meiner hugo.toml. Entwürfe fehlen im Build übrigens schon deshalb, weil hugo ohne -D aufgerufen wird.
  • easingthemes/ssh-deploy: Überträgt den Inhalt von hugo/public/ per rsync über SSH auf den Zielserver. Das Flag --delete in den ARGS entfernt auf dem Server Dateien, die im neuen Build nicht mehr vorkommen, damit keine verwaisten alten Seiten liegen bleiben.
  • GitHub Secrets: SSH_PRIVATE_KEY, SERVER_IP und SERVER_PORT werden unter Settings → Secrets and variables → Actions hinterlegt, damit keine Zugangsdaten im Klartext im Repository stehen. Der zugehörige öffentliche Schlüssel liegt in der Datei authorized_keys des Deploy-Users auf dem Server.

Für die Beispiele in diesem Beitrag habe ich die echte Domain durch einen Platzhalter ersetzt. In der produktiven Datei steht an dieser Stelle die tatsächliche Adresse des Blogs. Wer den Workflow nur manuell auslösen möchte, entfernt den push-Trigger und behält ausschließlich workflow_dispatch.

Theme und Hugo aktuell halten

Ein Submodule bleibt auf dem Stand stehen, den es beim Einbinden hatte. Ein einfaches git submodule update holt keine neue Theme-Version, sondern stellt nur den Stand wieder her, auf den das Repository verweist. Für ein echtes Update braucht es den Zusatz --remote. Die Befehle laufen wieder im Hauptverzeichnis des Repositories:

cd C:\sources\aaron_de
git submodule update --remote hugo/themes/hugo-PaperMod
cd hugo
hugo server -D

Wenn die Seite lokal fehlerfrei aussieht, committe und pushe ich den neuen Verweis. Die Pipeline baut die Seite dann mit der neuen Theme-Version und spielt sie auf den Server:

cd C:\sources\aaron_de
git add hugo/themes/hugo-PaperMod
git commit -m "Update PaperMod theme"
git push

Fazit: Weniger Komplexität, mehr Kontrolle

Der Umstieg von WordPress zu Hugo war für mich vor allem ein Schritt zu weniger beweglichen Teilen. Keine Datenbank, keine serverseitige Ausführung, keine ständigen Sicherheitsupdates für ein CMS. Stattdessen ein Satz statischer Dateien, die sich über Git versionieren und auf praktisch jeder Infrastruktur ausliefern lassen.

Zusammen mit der GitHub Actions Pipeline entsteht ein Setup, in dem der Windows-Rechner als reine Testumgebung dient, GitHub der maßgebliche Speicherort für alle Inhalte ist und der Live-Server ausschließlich fertig gebaute, statische Dateien erhält.

Inzwischen habe ich auch einige andere Projekte auf Hugo umgezogen und bin mit dieser Entscheidung nach wie vor sehr zufrieden.