WordPress 404 Debugging auf Plesk: Wie ein geteilter Redis Cache zwei Websites zerstörte.
TL;DR: Meine WordPress-Seite zeigte plötzlich einen hartnäckigen 404-Fehler. Nach 8+ Stunden WordPress Debugging stellte sich heraus: Zwei WordPress-Installationen auf demselben Plesk-Server teilten sich einen Redis Cache ohne Key-Trennung — und getenv() in der wp-config.php lieferte die Datenbank-Credentials einer völlig anderen Domain.
Es war ein Freitagabend, als meine WordPress-Seite nur noch einen HTTP 404 zurückgab. Keine WordPress-Fehlerseite, kein weißer Screen of Death — Chrome zeigte schlicht: „Diese Seite wurde nicht gefunden.“
Wer WordPress auf Plesk hostet, kennt die Frage: Liegt es an Apache? An Nginx? An der PHP-Config? An WordPress selbst? Bei Plesk WordPress Setups gibt es viele Schichten, die schiefgehen können.
Das WP-Admin unter /wp-admin/ war erreichbar. Soweit normal. Aber auf der Login-Seite standen zwei Dinge, die dort nicht hingehörten:
„Seite 2“ als Seitenname — und der Link unter dem Login-Formular zeigte „← Zurück zu Seite 2“, obwohl die URL eindeutig meine eigene Domain war.
Eine völlig andere Kunden-Website auf demselben Plesk-Server. Deren Name, deren Branding auf meiner Login-Seite. Ab hier wurde es interessant.
Vor dem WordPress Debugging: Backup und richtige Benutzerrechte
Bevor man auf einem Plesk-Server mit dem Debugging beginnt, zwei Grundregeln:
1. Datenbank-Backup erstellen. Jede Änderung an wp-config.php, jeder wp cache flush, jeder wp search-replace kann Daten unwiderruflich verändern. In meinem Fall hätte ein aktuelles Backup Stunden gespart — die vorhandenen UpdraftPlus-Backups enthielten bereits die falschen Daten.
2. Niemals als root arbeiten. Auf einem Plesk-Server hat jede Domain einen eigenen System-User (z.B. <your-site.com>_a1b2c3). WP-CLI Befehle, Dateiänderungen und jede Interaktion mit dem WordPress-Verzeichnis müssen als dieser User ausgeführt werden. Wer als root arbeitet, überschreibt die File-Permissions — und dann kann PHP-FPM die Dateien nicht mehr lesen oder WordPress kann nicht mehr in wp-content/ schreiben.
Tipp: Backup erstellen und als Domain-User arbeiten:
# 1. Datenbank-Backup BEVOR irgendwas geändert wird
cd /var/www/vhosts/<your-site.com>/httpdocs
wp db export ../backup-$(date +%Y%m%d-%H%M%S).sql
# 2. Als Domain-User einloggen (nicht als root!)
su - <your-site.com>_username
# Oder von root aus Befehle als User ausführen:
sudo -u <your-site.com>_username wp option get blogname
# 3. Falls Permissions bereits kaputt sind, reparieren:
chown -R <your-site.com>_username:psacln /var/www/vhosts/<your-site.com>/httpdocs/
find /var/www/vhosts/<your-site.com>/httpdocs/ -type d -exec chmod 755 {} \;
find /var/www/vhosts/<your-site.com>/httpdocs/ -type f -exec chmod 644 {} \;
WordPress Debugging: Server-Logs und Apache-Konfiguration prüfen
Erster Schritt bei jedem WordPress 404 Debugging auf Plesk: Die Server-Logs in Echtzeit beobachten. Plesk legt für jede Domain drei relevante Log-Dateien an — Apache Error Log, Nginx Proxy Error Log und das Access Log mit Status-Codes.
In meinem Fall zeigten die Logs: / gibt 404 zurück, /wp-admin/ funktioniert mit Status 200. Apache meldete „client denied by server configuration“ — ein Hinweis auf falsche Directory-Konfiguration.
[error] [client 203.0.113.42:443] AH01630: client denied by server configuration: /var/www/vhosts/<your-site.com>/httpdocs/app/
Die Website nutzt ein Bedrock-ähnliches Setup mit WordPress in einem Unterverzeichnis — eine beliebte Architektur beim WordPress auf Plesk hosten, aber auch eine mit viel Potenzial für falsche Pfade.
Befunde (custom WordPress Setup):
DocumentRoot zeigte auf httpdocs/app/ statt httpdocs/ — WordPress‘ index.php lag eine Ebene höher
AllowOverride None war gesetzt — .htaccess Rewrite-Rules wurden komplett ignoriert
Die lokale plesk/apache.conf war nur Dokumentation, nicht die aktive Plesk-Server-Config
Korrekturen:
DocumentRoot auf httpdocs/ geändert
AllowOverride All in Plesk’s „Additional Apache directives“ eingefügt
Ergebnis: Der WordPress 404 wurde zu 403 Forbidden. Ein anderer Fehler, aber immerhin ein Fortschritt.
Tipp: Bei einem WordPress 404 auf Plesk immer zuerst die drei Log-Dateien parallel beobachten:
Die Status-Codes im Access Log verraten, ob der 404 von Apache (leerer Body), Nginx (Proxy-Fehler) oder WordPress (großer Response-Body) kommt.
WordPress Cache Probleme: WP Rocket und SSL-Konfiguration
Der 403 entstand, weil Apache zwar das richtige Verzeichnis fand, aber index.php nicht als DirectoryIndex erkannte. Nachdem das behoben war, folgten weitere Probleme:
WP Rocket Fatal Error: Das WordPress Debug Log lieferte den konkreten Hinweis — das Caching-Plugin referenzierte eine nicht vorhandene Cloudflare-Klasse und verursachte einen PHP Fatal Error bei jedem Request:
PHP Fatal error: Uncaught Error: Class 'WP_Rocket\Addons\Cloudflare\Cloudflare' not found
in .../wp-content/plugins/wp-rocket/inc/Engine/Optimization/LazyRenderContent/Frontend/Processor/Cloudflare.php:12
Ein typisches WordPress Cache Problem — wenn ein Cache-Plugin nicht korrekt installiert ist, kann es die gesamte Site lahmlegen. Fix: Alle Plugins per WP-CLI deaktiviert.
HTTP 421 Misdirected Request: Nach dem Plugin-Fix zeigte das Nginx Proxy Log den nächsten Fehler. Plesk nutzt Nginx als Reverse Proxy vor Apache, und die SSL/SNI-Konfiguration zwischen beiden war inkonsistent — ein bekanntes Problem beim WordPress auf Plesk hosten mit SSL.
[error] upstream sent invalid header while reading response header from upstream
[warn] "ssl_stapling" ignored, issuer certificate not found for certificate
"GET / HTTP/2.0" 421 0
Der entscheidende Moment: Nach dem SSL-Fix zeigte das Access Log Status 404, aber mit 49 KB Response-Body. Das war kein Apache-Fehler — das war eine vollständige WordPress-Seite. PHP lief, Apache routete korrekt, WordPress startete. Aber WordPress selbst gab 404 zurück. Das Problem war nicht der Webserver.
Tipp: Bei WordPress Cache Problemen das Debug Log und das Error Log parallel beobachten, und Plugins per WP-CLI deaktivieren:
# WordPress Debug Log (muss in wp-config.php aktiviert sein)
tail -f /var/www/vhosts/<your-site.com>/httpdocs/wp-content/debug.log
# Apache Error Log für PHP Fatal Errors
tail -f /var/www/vhosts/system/<your-site.com>/logs/error_log
# Alle Plugins deaktivieren, um Cache-Plugin-Fehler auszuschließen
wp plugin deactivate --all
# Nginx Proxy Log für SSL/Proxy-Fehler
tail -f /var/www/vhosts/system/<your-site.com>/logs/proxy_error_log
Ein 404 mit großem Response-Body (z.B. 49 KB) bedeutet: WordPress antwortet, aber findet den Inhalt nicht. Ein 404 mit 0 Bytes bedeutet: Apache oder Nginx blockieren den Request bevor WordPress ihn sieht.
WordPress 404 Debugging mit WP-CLI: Die Datenbank ist fremd
Ab hier wurde WP-CLI zum wichtigsten Werkzeug. Die systematische Prüfung der WordPress-Konfiguration ergab ein überraschendes Bild: Der blogname war fremd, die Startseite gehörte zu einer anderen Site, und der einzige User in der Datenbank war ein Admin von Seite 2.
$ wp option get blogname
Seite 2
$ wp option get siteurl
https://www.solid-creation.com/wp-admin
$ wp option get home
https://www.solid-creation.com
$ wp option get show_on_front
page
$ wp option get page_on_front
1680
$ wp post get 1680 --fields=ID,post_title,post_status,post_type
+-------------+--------------------------------------+
| Field | Value |
+-------------+--------------------------------------+
| ID | 1680 |
| post_title | Seite 2 – Willkommen |
| post_status | publish |
| post_type | page |
+-------------+--------------------------------------+
$ wp user list --fields=ID,user_login,user_email
+----+----------------+---------------------------+
| ID | user_login | user_email |
+----+----------------+---------------------------+
| 1 | admin_seite2 | admin@seite2-beispiel.com |
+----+----------------+---------------------------+
Jeder Eintrag in der Datenbank gehörte zu Seite 2. Natürlich gab WordPress einen 404 zurück: Das Theme und die Inhalte meiner Site existierten in dieser Datenbank nicht.
Aber wie? Die wp-config.php hatte doch die richtigen Credentials. Der entscheidende Check:
getenv() lieferte nicht den eigenen Datenbanknamen, sondern den von Seite 2. Der Fallback-Wert in der wp-config.php wurde nie erreicht.
Tipp: Bei einem WordPress 404 die Datenbank-Zuordnung systematisch mit WP-CLI prüfen:
# 1. Stimmt der Seitenname?
wp option get blogname
# 2. Stimmen die URLs?
wp option get siteurl
wp option get home
# 3. Welche Startseite ist konfiguriert?
wp option get show_on_front
wp option get page_on_front
# 4. Existiert die Startseite und gehört sie zur richtigen Site?
wp post get $(wp option get page_on_front) --fields=ID,post_title,post_status,post_type
# 5. Welche User sind in der Datenbank?
wp user list --fields=ID,user_login,user_email
# 6. Welche DB nutzt WordPress tatsächlich?
wp eval "echo 'DB: ' . DB_NAME . PHP_EOL;"
# 7. Falls getenv() im Spiel ist: Was liefert es?
wp eval "var_dump(getenv('DB_NAME'));"
Wenn blogname, User oder Startseite nicht zur eigenen Site gehören, nutzt WordPress die falsche Datenbank.
Plesk WordPress: getenv() und PHP-FPM Pool-Kontamination
Die wp-config.php war wenige Stunden zuvor refactored worden — weg von hartcodierten Werten, hin zu einem getenv()-Pattern mit Fallback. Professionell gedacht, aber fatal in dieser Umgebung.
Das Problem:getenv('DB_NAME') gab einen Wert zurück — den Datenbanknamen von Seite 2. Der Fallback-Wert wurde nie erreicht.
Die Ursache: Auf einem Plesk-Server können PHP-FPM Pools unter bestimmten Umständen Umgebungsvariablen teilen. Oder die PHP-FPM Config enthielt env[DB_NAME] Direktiven, die auf die falsche Datenbank zeigten. Beide Domains auf demselben Plesk-Server bekamen dieselben Environment-Variablen — und damit dieselbe Datenbank.
Der Fix:getenv() entfernen und die Credentials direkt in die wp-config.php schreiben:
// VORHER (getenv kann Werte einer anderen Domain liefern):
define('DB_NAME', getenv('DB_NAME') ?: 'meine_db');
// NACHHER (direkt, keine Interferenz möglich):
define('DB_NAME', 'meine_db');
Nach der Änderung bestätigte WP-CLI die korrekte Datenbank:
$ wp eval "echo DB_NAME;"
meine_db
$ wp option get blogname
Meine Site
Tipp: Prüfen, ob getenv() unerwartete Werte liefert, und bei positivem Befund die Credentials hartcodieren:
# Prüfen, welche Werte getenv() zur Laufzeit liefert
wp eval "
foreach(['DB_NAME','DB_USER','DB_HOST'] as \$k) {
printf('%s = %s' . PHP_EOL, \$k, var_export(getenv(\$k), true));
}
"
# Falls getenv() fremde Werte liefert: In wp-config.php ersetzen
# getenv('DB_NAME') ?: 'meine_db' → 'meine_db'
# Danach verifizieren
wp eval "echo DB_NAME;"
wp option get blogname
WordPress Redis Cache: Gegenseitige Zerstörung durch fehlende Isolation
Nach dem getenv()-Fix zeigte die Site den richtigen Inhalt. Da die Permalinks noch nicht korrekt funktionierten, führte ich die Standard-Bereinigung aus:
$ cd /var/www/vhosts/solid-creation.com/httpdocs
$ wp cache flush
Success: The cache was flushed.
$ wp rewrite flush
Success: Rewrite rules flushed.
Meine Site lief. Erleichterung. Dann der Anruf: Seite 2 ging auf 404.
Also auf Seite 2 dasselbe:
$ cd /var/www/vhosts/seite2-beispiel.com/httpdocs
$ wp cache flush
Success: The cache was flushed.
$ wp rewrite flush
Success: Rewrite rules flushed.
Seite 2 lief wieder. Aber jetzt: Meine Site ging auf 404.
Ein Teufelskreis. Jedes wp cache flush zerstörte die jeweils andere Site. Aber diesmal war die Datenbank korrekt. Die DocumentRoots waren getrennt. Die PHP-FPM Sockets waren getrennt. Die Nginx/Apache-Configs referenzierten jeweils nur ihre eigene Domain.
Paralleles Log-Monitoring beider Sites bestätigte das wechselseitige Muster: Nach einem wp rewrite flush auf meiner Site wechselte Seite 2 von Status 200 auf 404. Nach einem wp rewrite flush auf Seite 2 war es umgekehrt. Immer nur eine Site konnte gleichzeitig laufen.
Die systematische Prüfung ergab: Beide Sites nutzten das Redis Object Cache Plugin mit identischer Konfiguration. Gleicher Redis-Server, gleiche Datenbank-Nummer, kein Key-Prefix.
Kein Key-Prefix. Das ist eines der häufigsten WordPress Cache Probleme auf Shared Hosting — und eines der am schwierigsten zu diagnostizierenden. WordPress speichert Options wie alloptions, rewrite_rules und siteurl im Redis Cache. Ohne Key-Prefix landen die Einträge beider Sites unter identischen Schlüsseln. Site A überschreibt Site B. Ein wp cache flush führt FLUSHALL aus und löscht den Cache beider Sites. Beim Neuaufbau gewinnt, wer zuerst schreibt.
Der Fix:WP_CACHE_KEY_SALT in jeder wp-config.php setzen, Redis leeren, Permalinks beider Sites neu generieren. Danach liefen beide Sites gleichzeitig — ohne sich gegenseitig zu zerstören.
// Site 1 wp-config.php
define('WP_CACHE_KEY_SALT', 'solid-creation.com_');
// Site 2 wp-config.php
define('WP_CACHE_KEY_SALT', 'seite2-beispiel.com_');
Tipp: WordPress Redis Cache Kollisionen diagnostizieren und beheben:
# 1. Paralleles Log-Monitoring beider Sites starten
# Terminal 1:
tail -f /var/www/vhosts/system/<your-site.com>/logs/proxy_access_ssl_log
# Terminal 2:
tail -f /var/www/vhosts/system/<your-site-2.com>/logs/proxy_access_ssl_log
# 2. Redis-Verbindung und Key-Prefix prüfen
wp redis status
wp eval "echo defined('WP_CACHE_KEY_SALT') ? WP_CACHE_KEY_SALT : 'NICHT GESETZT';"
# 3. Object Cache Drop-In beider Sites vergleichen
ls -la /var/www/vhosts/<your-site.com>/httpdocs/wp-content/object-cache.php
ls -la /var/www/vhosts/<your-site-2.com>/httpdocs/wp-content/object-cache.php
# 4. Fix: WP_CACHE_KEY_SALT in beide wp-config.php eintragen (s.o.)
# 5. Redis komplett leeren und Permalinks beider Sites neu generieren
redis-cli FLUSHALL
cd /var/www/vhosts/<your-site.com>/httpdocs && wp rewrite flush
cd /var/www/vhosts/<your-site-2.com>/httpdocs && wp rewrite flush
# 6. Verifizieren: Beide Sites antworten mit Status 200
curl -s -o /dev/null -w "%{http_code}" https://www.<your-site.com>/
curl -s -o /dev/null -w "%{http_code}" https://www.<your-site-2.com>/
WordPress auf Plesk hosten: Lessons Learned
getenv() in wp-config.php ist auf Shared Hosting gefährlich
Umgebungsvariablen sind nicht so isoliert wie man denkt. Auf einem Plesk-Server mit mehreren Domains können PHP-FPM Pools, Apache SetEnv-Direktiven oder systemweite Environment-Variablen Werte liefern, die nicht für die eigene Site gedacht sind. Wer WordPress auf Plesk hostet: DB-Credentials hartcodieren.
WordPress Redis Cache braucht immer Key-Prefixes
Wenn mehr als eine WordPress-Installation denselben Redis nutzt, ist WP_CACHE_KEY_SALT nicht optional — es ist Pflicht. Ohne Trennung ist ein FLUSHALL oder FLUSHDB eine Operation, die alle Sites auf dem Server trifft. Das gilt für jeden Plesk-Server mit mehreren WordPress-Sites.
WordPress 404 Debugging: Symptome können irreführen
„404“ deutete auf Apache/Nginx-Config — war aber WordPress, das die falsche DB las
„Falscher Seitenname“ deutete auf kompromittierte DB — war getenv() mit falschen Env-Vars
„Rewrite flush killt andere Site“ deutete auf geteiltes DocumentRoot — war geteilter WordPress Redis Cache
Jedes Symptom zeigte in eine andere Richtung. Nur systematisches WordPress Debugging mit WP-CLI und schrittweisem Ausschließen führte zur Lösung.
Konfiguration prüfen heißt Laufzeit prüfen
„Die Datenbanken sind getrennt“ stimmte auf Konfigurationsebene. Verschiedene DB-Namen, verschiedene Credentials, verschiedene Plesk-Subscriptions. Aber der Zugriffspfad zur Datenbank lief über getenv(), und der war kontaminiert. Getrennt konfiguriert bedeutet nicht getrennt zur Laufzeit.
Nie als root auf dem WordPress-Verzeichnis arbeiten
Auf einem Plesk-Server hat jede Domain einen eigenen System-User. WP-CLI, Dateibearbeitung und jede Interaktion mit dem httpdocs/-Verzeichnis muss als dieser User erfolgen. Wer als root arbeitet, überschreibt die File-Permissions — und dann kann PHP-FPM die Dateien nicht mehr lesen. Im schlimmsten Fall sieht man nur einen weißen Bildschirm, und die Ursache ist nicht sofort erkennbar.
Backups sind nicht optional
Jede Änderung an wp-config.php, jeder wp cache flush, jeder wp search-replace kann Daten verändern. Ein wp db export vor dem ersten Debugging-Schritt kostet 5 Sekunden und kann Stunden sparen. In meinem Fall enthielten die vorhandenen UpdraftPlus-Backups bereits die falschen Daten — ein manuelles SQL-Backup vor der Migration hätte den Rückweg verkürzt.
Plesk WordPress Hosting: Shared Hosting ist Shared Everything
Ein Plesk-Server mit mehreren Domains teilt sich mehr als nur Hardware: PHP-FPM, Redis, OPcache, manchmal sogar Temp-Verzeichnisse. Jede geteilte Ressource ist ein potenzieller Kollisionspunkt. Wer WordPress auf Plesk hostet, muss aktiv für Isolation sorgen — besonders beim WordPress Redis Cache.
Checkliste: WordPress auf Plesk sicher hosten
Für alle, die mehrere WordPress-Sites auf einem Plesk-Server betreiben:
Datenbank-Backup vor jeder Änderung erstellen (wp db export)
Als Domain-User arbeiten, nie als root auf dem httpdocs/-Verzeichnis
WP_CACHE_KEY_SALT in jeder wp-config.php mit eindeutigem Prefix setzen
Keine getenv()-Aufrufe für DB-Credentials auf Shared Hosting
Separate PHP-FPM Pools pro Domain (Plesk-Standard, aber prüfen)
Redis Database-Nummer pro Site trennen (WP_REDIS_DATABASE) oder Key-Salt nutzen
Nach Plugin-Updates testen, ob Cache-Plugins noch korrekt konfiguriert sind
8 Stunden WordPress Debugging. 2 Root Causes. 1 Lektion: Wenn du WordPress auf Plesk hostest, ist nichts wirklich getrennt, bis du es explizit trennst.