Eine Website braucht ein Zuhause. Meines steht im Heizungsraum: ein kleiner Server, den ich „Cube“ nenne. Dort wohnen schon einige Dienste. Jetzt zieht WordPress ein.
WordPress ist die Software, mit der ich die Seiten schreibe. Damit sie auf dem Cube ordentlich Platz hat, bekommt sie eine eigene kleine Wohnung. Das nennt sich Container: ein abgeschlossener Raum, in dem ein Programm mit allem läuft, was es braucht. Drei Räume genügen: einer für WordPress, einer für die Datenbank, in der alle Texte liegen, und einer für ein Werkzeug, mit dem ich WordPress per Befehl steuern kann.
Damit andere die Seite erreichen, braucht es einen Türsteher. Auf dem Cube übernimmt das ein Programm namens Caddy. Es nimmt Besucher an der Haustür entgegen, besorgt das Schloss für die verschlüsselte Verbindung (das kleine Schloss-Symbol im Browser) und führt Besucher zum richtigen Raum.
Dann kam der Moment, der mich ins Nachdenken brachte. Kaum stand die Tür offen, klopften Fremde an. Bots, also Programme, die das Internet nach offenen Türen absuchen, probierten Adressen wie /.env und /.git/config aus. Sie suchten nach vergessenen Schlüsseln. Auch die Installationsseite von WordPress wurde aufgerufen. Wer sie vor mir abgeschickt hätte, wäre Besitzer meiner Seite geworden.
Ich habe Caddy deshalb ein Passwort aufgetragen. Seitdem fragt die Tür zuerst nach Namen und Kennwort, erst danach geht es weiter. Ich habe im Protokoll nachgesehen: Niemand hat die Installation abgeschickt. Danach habe ich WordPress selbst eingerichtet.
Zum Fundament gehört ein Wachdienst. WP Umbrella ist ein Dienst, der meine Seite von außen im Blick behält und mir Bescheid gibt, wenn etwas nicht stimmt. Dafür muss er anklopfen dürfen. Mit dem Passwort an der Tür blieb er jedoch draußen: Die Meldungen im Protokoll von Caddy zeigten mehrfach dieselbe Absage. Ich habe ihm deshalb eine schmale Seitentür geöffnet. Sie gilt nur für seine Absenderadresse und sein Kennzeichen. Alle anderen Besucher fragt die Tür weiter nach dem Passwort.
Eine Sache bleibt offen. Ich wollte den Zugang zusätzlich auf mein Heimnetz begrenzen. Caddy sieht von jedem Besucher jedoch dieselbe Absenderadresse, nämlich die der Haustür im Docker-Netz. Diese Regel hätte also nichts bewirkt. Ich habe sie herausgenommen und das Thema auf die Liste gesetzt.
Ergebnis: Das Backend ist per HTTPS erreichbar, hinter Passwort, mit leerem WordPress im Standard-Aussehen und ohne zusätzliche Plugins. Das Fundament steht.
Zeig mir, wie es geht: Stack und Verzeichnisse
- Stack
kispunter/data/terruhn.it/kisp/mitcompose.yamlund.env(Rechte600). - Dienste:
wordpress(wordpress:php8.3-apache),db(mariadb:11.4),wpcli(wordpress:cli, Profiltools, startet nur bei Bedarf). - Netze:
kisp_internal(internal: true, nur WordPress und Datenbank), das Standardnetz für Internetzugang ausgehend,terruhn_proxy(extern) für Caddy. - Keine veröffentlichten Ports. Zusätzlicher Mount
/data/terruhn.it/kisp/stagingals Exportziel für Etappe 03. - Verzeichnisse und Rechte:
mkdir -p /data/terruhn.it/kisp/{wordpress,db,staging}
chown 33:33 /data/terruhn.it/kisp/wordpress /data/terruhn.it/kisp/staging
cd /data/terruhn.it/kisp && docker compose -p kisp up -d
- Konfiguration im Repository unter
infra/:compose.yaml,.env.example,Caddyfile-kisp.
Zeig mir, wie es geht: Caddy mit Passwortschutz
- Hash erzeugen:
docker exec -it caddy caddy hash-password. - Site-Block (Hash nur auf dem Server eintragen, nicht ins Repository):
wordpress.thekeepitsimpleproject.org {
encode zstd gzip
log
@geschuetzt not {
remote_ip 212.83.142.5
header_regexp ua User-Agent ^WPUmbrella
}
basic_auth @geschuetzt {
rene HASH_HIER
}
reverse_proxy kisp-wp:80
}
- Der Matcher
@geschuetzttrifft alle Anfragen außer denen von WP Umbrella (Absenderadresse212.83.142.5und User-Agent^WPUmbrella). Nur dort entfällt die Passwortabfrage. Das Plugin sichert seine Routen selbst mit Token und Signatur. Weitere Absenderadressen von WP Umbrella sind möglich (unbekannt); neue 401-Zeilen im Log zeigen sie an. - Prüfen und laden:
docker exec caddy caddy validate --config /etc/caddy/Caddyfile
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
- Zertifikat von Let’s Encrypt über den HTTP-Challenge, Ports 80 und 443 vom Router auf den Cube weitergeleitet.
- Stolperstein: Caddy sieht jede Anfrage mit
172.18.0.1(Docker-Gateway). Eineremote_ip-Beschränkung auf das LAN greift deshalb nicht und lässt alles durch. Lesbar mitlogim Site-Block unddocker logs caddy | grep '"host":"wordpress' | grep -o '"remote_ip":"[^"]*"'.
Zeig mir, wie es geht: WP Umbrella anbinden
- Plugin aus dem WordPress-Verzeichnis installieren, API-Schlüssel eintragen.
- Befund: Die Felder für die HTTP-Auth-Zugangsdaten erscheinen nur, wenn eine
.htpasswdneben einer.htaccessliegt. Der Schutz liegt hier in Caddy, daher fehlten sie. Ein Must-Use-Plugin blendet sie über den Filterwp_umbrella_has_htpasswdein:
mkdir -p /data/terruhn.it/kisp/wordpress/wp-content/mu-plugins
vi /data/terruhn.it/kisp/wordpress/wp-content/mu-plugins/kisp-umbrella-httpauth.php
chown -R www-data:www-data /data/terruhn.it/kisp/wordpress/wp-content/mu-plugins
Inhalt: add_filter('wp_umbrella_has_htpasswd', '__return_true');
- Befund: Die Anfragen der WP-Umbrella-Server trugen die Zugangsdaten dennoch nicht mit und liefen auf 401. Daher die Ausnahme im Caddyfile (oben).
- Absender im Log prüfen:
docker logs caddy --since 5m 2>&1 | grep '"remote_ip":"212.83.142.5"' | jq -r '[(.ts|todate), .request.method, .request.uri[0:70], .status] | @tsv'
- Uptime-Monitor bleibt deaktiviert, die Synchronisierung läuft.
Zeig mir, wie es geht: WP-CLI und Prüfung
cd /data/terruhn.it/kisp
docker compose -p kisp run --rm wpcli wp core version
docker compose -p kisp run --rm wpcli wp core is-installed 2>/dev/null; echo "Exit: $?"
docker compose -p kisp run --rm wpcli wp theme list --status=active --field=name
docker compose -p kisp run --rm wpcli wp plugin list --format=count
wp core is-installed schreibt keine Meldung, das Ergebnis steckt im Exit-Code (0 installiert, 1 nicht). Stand dieser Etappe: WordPress 7.1.2, Theme twentytwentyfive, 0 Plugins. Die Warnung zu HTTP_HOST stammt aus der Konfiguration ohne Webanfrage und ist harmlos.
Angriffe im Log nach uri zählen:
docker logs caddy --since 24h 2>&1 | grep '"host":"wordpress' | grep -o -E '"uri":"[^"]*"' | sort | uniq -c | sort -rn | head