WordPress 404 HTTP Status Fix in Plesk Hosting

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.

Error 404 Page not found Beispielbild

WordPress 404: Wenn die eigene Seite verschwindet

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:

# Terminal 1: Apache Error Log
tail -f /var/www/vhosts/system/<your-site.com>/logs/error_log
# Terminal 2: Nginx Proxy Error Log
tail -f /var/www/vhosts/system/<your-site.com>/logs/proxy_error_log
# Terminal 3: Access Log mit Status-Codes
tail -f /var/www/vhosts/system/<your-site.com>/logs/proxy_access_ssl_log

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:

$ wp eval "var_dump(getenv('DB_NAME'));"
string(10) "seite2_db"

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.

$ wp redis status
Status: Connected
Host: 127.0.0.1
Port: 6379
Database: 0

$ wp eval "echo defined('WP_CACHE_KEY_SALT') ? WP_CACHE_KEY_SALT : 'NICHT GESETZT';"
NICHT GESETZT

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 alloptionsrewrite_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
  • OPcache Isolation prüfen (opcache.validate_timestamps = 1)
  • 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.