Zum Inhalt springen
Serverküche
Suche

Die Suche wird geladen … (nur in der veröffentlichten Seite verfügbar).

Anwendungen Schwierigkeit: Fortgeschritten

WordPress mit Docker & Traefik sauber betreiben

WordPress mit Docker und MariaDB hinter Traefik aufsetzen – mit HTTPS, isolierter Datenbank und ehrlicher Update-Strategie statt Wartungs-Albtraum.

· 6 Min. Lesezeit ·Dauer: ca. 60 Minuten
Inhaltsverzeichnis

WordPress betreibt einen großen Teil des Webs – und ist berüchtigt für gehackte Installationen. Der Unterschied zwischen „läuft und ist sicher“ und „wird zur Spam-Schleuder“ liegt im sauberen Aufbau und in ehrlicher Wartung. Dieses Rezept liefert beides: WordPress mit Docker, isolierter Datenbank, HTTPS – und einer klaren Ansage, was du dauerhaft tun musst.

Was bauen wir?

Eine produktionstaugliche WordPress-Installation mit WordPress 7.1.0 (PHP 8.5) und MariaDB 11.8 hinter Traefik. Die Datenbank läuft in einem abgeschotteten internen Netz ohne Internetzugang, WordPress erhält sein HTTPS von Traefik, und wir lösen den klassischen Reverse-Proxy-Stolperstein (Redirect-Schleife/Mixed-Content) sauber. Am Ende hast du einen laufenden Blog – und weißt, wie du ihn sicher hältst.

WordPress oder Hugo?

Wenn du „nur“ eine Website oder einen Blog ohne interaktive Funktionen brauchst, ist eine statische Seite mit Hugo im Betrieb sicherer und wartungsärmer (keine Datenbank, keine Angriffsfläche). WordPress lohnt sich, wenn du das riesige Plugin-Ökosystem, Redakteurs-Workflows oder Shop-/Mitglieder-Funktionen brauchst. Dieses Tutorial nimmt WordPress ehrlich, inklusive seiner Wartungslast.

Voraussetzungen

🍳 Empfehlung Anzeige

VPS 2000 G12.5

8 vCore · 16 GB RAM · 256 GB SSD

ab 26,92 €/Monat

WordPress mit Datenbank und PHP läuft entspannt auf dem VPS 2000.

Zu netcup →

💶 5 € Gutschein für netcup-Neukunden: (nicht für Domains und VPS Lite)

Im Warenkorb einlösen →

Schritt für Schritt

Schritt 1: Zwei Container, zwei Netze

WordPress besteht aus zwei Teilen: der PHP-Anwendung und einer MariaDB-Datenbank. Ein sauberes Setup trennt beide und schottet die Datenbank ab – sie soll nur von WordPress erreichbar sein und braucht selbst kein Internet. Genau dafür nutzen wir zwei Netze (siehe Docker-Netzwerke verstehen): das öffentliche proxy-Netz zwischen WordPress und Traefik, und ein internal-Netz zwischen WordPress und Datenbank.

Schema: Browser per HTTPS zu Traefik, von dort per HTTP mit X-Forwarded-Proto zu WordPress, WordPress per SQL zur MariaDB im internen Netz
Traefik terminiert TLS und meldet WordPress per X-Forwarded-Proto die HTTPS-Herkunft; die MariaDB liegt ohne Internetzugang im internen Netz

Terminal
mkdir -p /opt/wordpress && cd /opt/wordpress

Schritt 2: Die Compose-Datei

Ersetze DEINE_DOMAIN und alle Passwörter durch eigene Werte:

YAML
services:
  db:
    image: mariadb:11.8
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: DEIN_DB_PASSWORT
      MARIADB_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - db_data:/var/lib/mysql
    networks: [intern]
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      start_period: 10s
      start_interval: 2s
      interval: 30s
      timeout: 5s
      retries: 5

  wordpress:
    image: wordpress:7.1.0-php8.5-apache
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: DEIN_DB_PASSWORT
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_CONFIG_EXTRA: |
        if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $$_SERVER['HTTPS'] = 'on'; }
        define('WP_HOME', 'https://DEINE_DOMAIN');
        define('WP_SITEURL', 'https://DEINE_DOMAIN');
    volumes:
      - wp_data:/var/www/html
    networks: [proxy, intern]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.wp.rule=Host(`DEINE_DOMAIN`)"
      - "traefik.http.routers.wp.entrypoints=websecure"
      - "traefik.http.routers.wp.tls.certresolver=le"
      - "traefik.http.services.wp.loadbalancer.server.port=80"

volumes:
  db_data: {}
  wp_data: {}
networks:
  proxy:
    external: true
  intern:
    internal: true

Drei Details sind hier entscheidend:

  • depends_on: condition: service_healthy – WordPress startet erst, wenn die Datenbank per Healthcheck als bereit gemeldet wird. Ohne das scheitert der erste Start in einer Race Condition.
  • intern ist internal: true – die Datenbank hängt nur in diesem Netz und hat damit keinen Internetzugang; WordPress erreicht sie unter dem Hostnamen db.
  • WORDPRESS_CONFIG_EXTRA – der wichtigste Teil (nächster Schritt).

Der Reverse-Proxy-Stolperstein: HTTPS erkennen

Traefik terminiert TLS und spricht intern per HTTP mit WordPress. WordPress denkt deshalb, es liefe unsicher, und erzeugt http://-Links – das führt zu Mixed-Content-Fehlern oder einer Redirect-Schleife (ERR_TOO_MANY_REDIRECTS). Die Zeile if (... HTTP_X_FORWARDED_PROTO ... === 'https') { $_SERVER['HTTPS'] = 'on'; } sagt WordPress, dass die ursprüngliche Verbindung HTTPS war. Zusammen mit WP_HOME/WP_SITEURL ist das die saubere Lösung. In Compose müssen die PHP-Variablen als $$ geschrieben werden, sonst interpretiert Docker sie als eigene Variablen.

Schritt 3: Starten

Terminal
docker compose up -d

Docker startet zuerst die Datenbank, wartet auf ihren Healthcheck und startet dann WordPress. Prüfe:

Terminal
docker compose ps
Ausgabe
NAME                    IMAGE                           SERVICE     STATUS
wordpress-db-1          mariadb:11.8                    db          Up (healthy)
wordpress-wordpress-1   wordpress:7.1.0-php8.5-apache   wordpress   Up

Prüfe von außen, dass WordPress mit gültigem HTTPS antwortet (die 302 leitet zur Installation):

Terminal
curl -sI https://DEINE_DOMAIN/ | head -1
Ausgabe
HTTP/2 302

Schritt 4: Die berühmte 5-Minuten-Installation

Öffne https://DEINE_DOMAIN/. WordPress leitet zum Installationsassistenten. Nach der Sprachwahl (Deutsch) füllst du die Grunddaten aus:

Der WordPress-Installationsassistent mit Feldern für Titel, Benutzername, Passwort und E-Mail
Die 5-Minuten-Installation: Website-Titel, Admin-Konto und E-Mail

Kein „admin“ als Benutzername

Wähle nicht den Benutzernamen admin – er ist das erste Ziel jedes Brute-Force-Angriffs auf WordPress. Nimm einen eigenen Namen und ein starkes Passwort. Das ist die einfachste und wirksamste Härtungsmaßnahme überhaupt.

Nach dem Klick auf „WordPress installieren“ und dem ersten Login landest du im Dashboard:

Das WordPress-Dashboard nach der Installation, Version 7.1 mit dem Theme Twenty Twenty-Five
Das Dashboard: hier verwaltest du Beiträge, Seiten, Design und Plugins

Schritt 5: Die Website ist live

Deine Seite ist sofort öffentlich erreichbar – mit gültigem HTTPS von Traefik, ohne Mixed-Content-Fehler dank der Proxy-Konfiguration:

Das Frontend der frisch installierten WordPress-Seite mit dem ersten Beitrag „Hallo Welt!"
Das Frontend unter der eigenen Domain – der Standard-Beitrag „Hallo Welt!“

Schritt 6: Grundlegende Härtung

WordPress ist ab Werk funktional, aber nicht optimal abgesichert. Die wichtigsten Sofortmaßnahmen:

  • Automatische Updates aktivieren: Unter Dashboard → Aktualisierungen sicherstellen, dass zumindest Sicherheitsupdates automatisch eingespielt werden.
  • Unnötige Standard-Inhalte löschen: Der „Hallo Welt!“-Beitrag, die Beispiel-Seite und das mitgelieferte Beispiel-Plugin (Hello Dolly) können weg.
  • Weniger ist mehr bei Plugins: Jedes Plugin ist zusätzliche Angriffsfläche. Installiere nur, was du wirklich brauchst, und nur aus vertrauenswürdiger Quelle.
  • Login zusätzlich schützen: Vor die WordPress-Anmeldung (/wp-login.php) gehört idealerweise eine weitere Hürde. Bewährt sind eine Rate-Limit-/BasicAuth-Middleware in Traefik oder ein 2FA-Plugin. Auch Fail2ban kann WordPress-Login-Versuche auswerten.
  • XML-RPC im Blick behalten: Die Datei xmlrpc.php ist ein häufiges Ziel für Brute-Force und DDoS. Wird sie nicht gebraucht, per Traefik-Middleware oder Plugin sperren.

Wenn es nicht funktioniert

Redirect-Schleife (ERR_TOO_MANY_REDIRECTS) oder Mixed-Content-Warnungen. Die HTTPS-Erkennung fehlt. Prüfe, dass der WORDPRESS_CONFIG_EXTRA-Block mit HTTP_X_FORWARDED_PROTO und den $$-Zeichen exakt gesetzt ist (siehe Warnung in Schritt 2), und starte den WordPress-Container neu.

WordPress startet nicht, meldet „Error establishing a database connection“. Meist eine Race Condition oder ein Passwort-Mismatch. Prüfe, dass depends_on: condition: service_healthy gesetzt ist und WORDPRESS_DB_PASSWORD exakt MARIADB_PASSWORD entspricht. Logs: docker compose logs db.

Der erste Start scheitert, obwohl später alles läuft. Die Datenbank braucht beim allerersten Start einige Sekunden zur Initialisierung. Mit dem Healthcheck ist das abgedeckt; ohne ihn hilft ein erneutes docker compose up -d.

Medien-Uploads schlagen bei großen Dateien fehl. PHP begrenzt die Upload-Größe. Lege eine eigene uploads.ini an und mounte sie nach /usr/local/etc/php/conf.d/ mit z. B. upload_max_filesize = 64M und post_max_size = 64M.

Nach einem Domainwechsel zeigt die Seite ins Leere. WP_HOME/WP_SITEURL in der Compose-Datei stehen fest verdrahtet – bei einer neuen Domain dort anpassen und Container neu starten (die Werte überschreiben die Datenbank-Einstellung).

Wartung & Backups

  • Ehrlich: WordPress ist Wartungsarbeit. Anders als eine statische Seite läuft hier aktiver Code mit Datenbank – Kern, Themes und Plugins bekommen regelmäßig Sicherheitsupdates, die zeitnah eingespielt werden müssen. Ein vernachlässigtes WordPress wird zuverlässig gekapert. Aktiviere automatische Updates und prüfe monatlich den Aktualisierungsstatus. Den Container-Tag (wordpress:7.1.0-php8.5-apache) hältst du über deinen normalen Update-Prozess aktuell.
  • Backup ist zweiteilig – beides ist Pflicht. WordPress besteht aus Datenbank (Beiträge, Einstellungen, Nutzer) und Dateien (wp_data-Volume: Uploads, Themes, Plugins). Ein Restic-Snapshot des laufenden MariaDB-Volumes allein ist keine Garantie – sichere die Datenbank zusätzlich per Dump. Wichtig sind dabei -T (sonst hängt der Befehl an der Passwortabfrage und legt eine leere Datei an) und das Passwort direkt am -p, ohne Leerzeichen:
Terminal
docker compose exec -T db mariadb-dump -u wordpress -p'DEIN_DB_PASSWORT' --single-transaction wordpress > backup.sql

Prüfe danach unbedingt, dass die Datei nicht leer ist (ls -l backup.sql). Dump und wp_data-Volume gehören ins Restic-Backup.

  • Wiederherstellung testen. Ein Backup, das nie zurückgespielt wurde, ist nur eine Hoffnung – spiele einen Restore mindestens einmal probeweise durch, damit du im Ernstfall weißt, dass Dump und Dateien zusammenpassen.

Feedback per E-Mail: feedback@serverkueche.de

Das könnte dir auch schmecken

linkding: Lesezeichen selbst hosten
Anwendungen Fortgeschritten

linkding: Lesezeichen selbst hosten

Alle Lesezeichen an einem Ort, geräteübergreifend und ohne Browser-Zwang: linkding als schlanker Bookmark-Manager hinter …

· 4 Min. Lesezeit