WordPress-Probleme selbst beheben

Schritt-für-Schritt-Lösungen für die Probleme, die WP Monitor meldet. Erstellen Sie vor jeder Änderung ein vollständiges Backup (Dateien und Datenbank). Serverregeln stehen jeweils für Apache (in der Datei .htaccess im Hauptverzeichnis; LiteSpeed liest dieselbe Datei) und für nginx (in der Website-Konfiguration; nginx danach neu laden).

WordPress aktualisieren

Veraltetes WordPress ist der häufigste Weg, über den Websites gehackt werden. Sicherheitskorrekturen erscheinen nur für die aktuelle Version; eine Version mit bekannter Sicherheitslücke sollte noch am selben Tag aktualisiert werden.

Mit Shell-Zugang erledigt WP-CLI dasselbe im Website-Ordner:

WP-CLI
wp core update
wp core update-db
Nach oben

Plugins und Themes aktualisieren

Plugins und Themes sind der zweite häufige Angriffsweg: Die meisten WordPress-Sicherheitsmeldungen betreffen sie.

WP-CLI
wp plugin update --all
wp theme update --all
Nach oben

Seiten-Cache aktivieren

Ohne Seiten-Cache baut WordPress jede Seite bei jedem Besuch aus PHP und Datenbank neu. Ein Seiten-Cache speichert die fertige Seite und liefert sie direkt aus – meist um ein Vielfaches schneller und deutlich belastbarer bei Besucherspitzen.

Verwenden Sie immer nur ein Seiten-Cache-Plugin. Manche Hoster (SiteGround, Kinsta, WP Engine u. a.) cachen Seiten bereits auf dem Server; dann ist dafür kein Plugin nötig.

Nach oben

Komprimierung aktivieren

Komprimierung (Gzip oder Brotli) macht Seiten, CSS und JavaScript auf dem Weg zum Besucher 60–80 % kleiner. Die Cache-Plugins oben können sie einschalten, oder sie wird in der Serverkonfiguration gesetzt:

Apache (.htaccess)
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml
  AddOutputFilterByType DEFLATE application/javascript text/javascript application/json
  AddOutputFilterByType DEFLATE application/xml application/rss+xml image/svg+xml
</IfModule>
nginx
# in the http { } or server { } block
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types text/plain text/css text/xml application/javascript text/javascript
           application/json application/xml application/rss+xml image/svg+xml;
Nach oben

CSS und JavaScript im Browser zwischenspeichern

Werden CSS, JavaScript, Bilder und Schriften mit langer Cache-Dauer ausgeliefert, laden wiederkehrende Besucher und jede weitere Seite sie aus dem Browser statt neu herunter. WordPress versieht diese Dateien mit einer Version (?ver=...), die sich bei Änderungen ändert – eine lange Dauer ist also unbedenklich.

Die meisten Cache-Plugins haben eine Option „Browser-Caching“. Oder auf dem Server einstellen:

Apache (.htaccess)
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/css "access plus 1 month"
  ExpiresByType text/javascript "access plus 1 month"
  ExpiresByType application/javascript "access plus 1 month"
  ExpiresByType image/webp "access plus 1 month"
  ExpiresByType image/jpeg "access plus 1 month"
  ExpiresByType image/png "access plus 1 month"
  ExpiresByType image/svg+xml "access plus 1 month"
  ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
nginx
# in the server { } block
location ~* \.(?:css|js|webp|jpe?g|png|gif|svg|ico|woff2?)$ {
    expires 30d;
    access_log off;
    try_files $uri =404;
}

nginx: Ein location-Block mit eigenen add_header-Zeilen übernimmt die add_header-Zeilen des server-Blocks nicht mehr. Wiederholen Sie dort gegebenenfalls die Sicherheits-Header.

Nach oben

Die erste Antwort beschleunigen

Das ist die Zeit, bis die Seite zu laden beginnt, gemessen von unserem Server aus. Über 1,5 Sekunden bedeutet meist, dass die Seite bei jedem Besuch neu erzeugt wird.

Nach oben

WordPress-Emoji-Skript deaktivieren

WordPress fügt in den <head> jeder Seite ein kleines Skript und CSS ein, um Emojis in Browsern, die sie nicht darstellen können, als Bilder zu zeichnen. Alle aktuellen Browser zeigen Emojis selbst an, es ist also nur zusätzlicher Code bei jedem Seitenaufruf.

Viele Optimierungs-Plugins haben eine Option „Emojis deaktivieren“ (Autoptimize, LiteSpeed Cache, Perfmatters u. a.), und das kleine Plugin „Disable Emojis“ macht nur das. Oder fügen Sie diesen Code ein:

PHP
// functions.php of a child theme, or a small custom plugin
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_enqueue_scripts', 'wp_enqueue_emoji_styles');
remove_action('wp_print_styles', 'print_emoji_styles');
add_filter('emoji_svg_url', '__return_false');
Nach oben

SSL-Zertifikat reparieren

Ist das Zertifikat für die Adresse nicht gültig, sehen Besucher statt der Website eine ganzseitige Browserwarnung („Ihre Verbindung ist nicht privat“), Suchmaschinen werfen sie aus dem Index, und Formulare und Anmeldungen sind nicht sicher. Was zu tun ist, hängt vom Befund ab:

Auf Shared Hosting über das Control Panel: AutoSSL in cPanel, „SSL It!“ oder Let’s Encrypt in Plesk, oder fragen Sie den Hoster. Auf dem eigenen Server holt und erneuert certbot Let’s-Encrypt-Zertifikate:

certbot
# free Let's Encrypt certificate, renewed automatically (run as root)
certbot --nginx -d example.com -d www.example.com     # nginx
certbot --apache -d example.com -d www.example.com    # Apache

Sobald HTTPS funktioniert, leiten Sie http:// auf https:// um:

Apache (.htaccess)
# .htaccess, above the # BEGIN WordPress block
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
nginx
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
Nach oben

Alle http://-Adressen auf https:// umleiten

Jede Adresse der Website sollte Besucher von http:// auf https:// weiterleiten, nicht nur die Startseite. Übernimmt WordPress die Umleitung (weil seine Adresse unter Einstellungen > Allgemein mit https:// beginnt, oder per Plugin), gilt sie nur für die Seiten, die WordPress selbst ausliefert: 404-Seiten, robots.txt und andere Dateien öffnen sich weiterhin unverschlüsselt und laden oft auch ihre Skripte und Stylesheets über http://. Scanner wie Sucuri SiteCheck melden diese Seiten. Eine Serverregel erfasst jede Adresse und ist zudem schneller: Der Besucher wird umgeleitet, bevor WordPress überhaupt lädt.

Viele Hoster bieten dafür einen Schalter: „Force HTTPS Redirect“ in cPanel (Domains), „Permanente, SEO-sichere 301-Weiterleitung von HTTP zu HTTPS“ in Plesk (Hosting-Einstellungen) oder „Always Use HTTPS“ in Cloudflare (SSL/TLS > Edge Certificates). Andernfalls fügen Sie die Regel selbst hinzu:

Apache (.htaccess)
# .htaccess, above the # BEGIN WordPress block
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
nginx
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
Nach oben

XML-RPC deaktivieren

xmlrpc.php ist eine alte Fernzugriffs-Schnittstelle. Angreifer probieren darüber Hunderte Passwörter in einer einzigen Anfrage. Die meisten Websites brauchen sie nicht mehr; das Jetpack-Plugin und manche Publishing-Apps schon – dann überspringen Sie diesen Schritt.

Apache (.htaccess)
<Files "xmlrpc.php">
  Require all denied
</Files>
nginx
location = /xmlrpc.php {
    deny all;
}

Auch Sicherheits-Plugins (Wordfence, Solid Security u. a.) können XML-RPC deaktivieren.

Nach oben

WordPress-Generator-Tag entfernen

Standardmäßig schreibt WordPress seine genaue Version in jede Seite: <meta name="generator" content="WordPress 7.1.2">, und in den RSS-Feed: <generator>https://wordpress.org/?v=7.1.2</generator>. Das verrät Angreifern, welche bekannten Lücken sie probieren können.

Entfernen Sie es mit der Option „WordPress-Version verbergen“ eines Sicherheits-Plugins (Wordfence, Solid Security u. a. haben eine) oder mit einem Must-Use-Plugin. Ein Must-Use-Plugin ist eine PHP-Datei im Ordner wp-content/mu-plugins: WordPress lädt sie automatisch, sie muss nicht aktiviert werden, kann im Adminbereich nicht versehentlich deaktiviert werden, und Theme- und Plugin-Updates lassen sie unberührt.

Legen Sie den Ordner mu-plugins in wp-content an, falls er noch nicht existiert (per FTP oder im Dateimanager Ihres Hostings), und darin eine Datei hide-wp-version.php mit diesem Inhalt:

wp-content/mu-plugins/hide-wp-version.php
<?php
// Plugin Name: Hide WordPress version
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
Nach oben

Zugriff auf readme.html sperren

Jede WordPress-Installation hat eine Datei readme.html im Hauptverzeichnis, und sie zeigt jedem, der sie öffnet, die WordPress-Version.

Löschen hält nicht lange: WordPress legt die Datei bei jedem Core-Update neu an. Sperren Sie stattdessen den Zugriff auf dem Server; die Regel bleibt nach Updates bestehen.

Apache und LiteSpeed: Fügen Sie dies in die Datei .htaccess im Hauptverzeichnis ein (der Ordner mit wp-config.php), oberhalb oder unterhalb des Blocks # BEGIN WordPress, nicht darin (WordPress schreibt diesen Block neu):

Apache (.htaccess)
<Files "readme.html">
  Require all denied
</Files>

nginx: Fügen Sie dies in den server { }-Block der Website ein, dann die Konfiguration prüfen und neu laden (nginx -t, dann systemctl reload nginx). Ein exakter location =-Treffer hat Vorrang vor den anderen Regeln:

nginx
location = /readme.html {
    deny all;
}
Nach oben

wp-admin/upgrade.php sperren

/wp-admin/upgrade.php ist die Seite, die nach einem Core-Update die WordPress-Datenbank aktualisiert. Jeder kann sie ohne Anmeldung öffnen, und ihr Quelltext zeigt die genaue WordPress-Version. WordPress lädt auf dieser Seite keine Plugins, daher erreicht die Option „Version verbergen“ eines Sicherheits-Plugins sie nicht.

Normale Updates brauchen sie nicht: Updates über Dashboard > Aktualisierungen, automatische Updates und wp core update-db (WP-CLI) aktualisieren die Datenbank selbst. Sperren Sie sie auf dem Server:

Apache (.htaccess)
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteRule ^wp-admin/upgrade\.php$ - [F,L]
</IfModule>

Setzen Sie die Apache-Regel über den Block # BEGIN WordPress, nicht hinein (WordPress schreibt diesen Block neu).

nginx
location = /wp-admin/upgrade.php {
    deny all;
}
Nach oben

Benutzernamen nicht auflisten

Standardmäßig listet /wp-json/wp/v2/users die Benutzernamen aller auf, die Inhalte veröffentlicht haben – die halbe Anmeldung für Angreifer. Dieser Code verbirgt die Liste vor nicht angemeldeten Besuchern; der Block-Editor funktioniert weiter:

PHP
// functions.php of a child theme, or a small custom plugin
add_filter('rest_endpoints', function ($endpoints) {
    if (!is_user_logged_in()) {
        unset($endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)']);
    }
    return $endpoints;
});

Die meisten Sicherheits-Plugins haben eine ähnliche Option („User Enumeration deaktivieren“).

Der RSS-Feed zeigt bei jedem Beitrag den Namen des Autors (<dc:creator>), und WordPress setzt diesen Namen auf den Benutzernamen, solange der Autor keinen anderen gewählt hat. Dafür ist kein Code nötig: Tragen Sie unter Benutzer > Profil für jeden Autor einen Spitznamen ein, der vom Benutzernamen abweicht, und wählen Sie ihn bei „Öffentlicher Name“ aus. Die Autorenzeile der Beiträge und die Autorenseiten zeigen dann ebenfalls diesen Namen.

Nach oben

Verzeichnisauflistung deaktivieren

Mit aktiver Verzeichnisauflistung kann jeder die Dateiliste eines Ordners wie /wp-content/uploads/ durchsehen, einschließlich privater Uploads und Backups.

Apache (.htaccess)
Options -Indexes
nginx
# nginx lists directories only where autoindex is switched on: remove it, or set
autoindex off;
Nach oben

Öffentliches Debug-Log entfernen

wp-content/debug.log kann Serverpfade, Plugin-Fehler und manchmal Datenbankdetails enthalten, und jeder kann die Datei herunterladen. Löschen Sie sie jetzt und verhindern Sie, dass WordPress sie dort erneut schreibt:

wp-config.php
// wp-config.php
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
// or, to keep logging, write the log outside the website folder:
// define('WP_DEBUG_LOG', '/home/USER/logs/wp-debug.log');

Und sperren Sie den Dateinamen, falls das Logging später wieder eingeschaltet wird:

Apache (.htaccess)
<Files "debug.log">
  Require all denied
</Files>
nginx
location ~* /debug\.log$ {
    deny all;
}
Nach oben

Apache-, nginx- und PHP-Version verbergen

Antwort-Header wie Server: nginx/1.30.5 oder X-Powered-By: PHP/8.2.12 verraten Angreifern genau, für welche Versionen sie bekannte Sicherheitslücken nachschlagen können. Der Servername allein (nginx, Apache) ist unbedenklich; verbergen sollten Sie die Versionsnummer. Das ersetzt keine Updates, es hört nur auf, die Version anzukündigen.

Apache: in der Haupt-Serverkonfiguration (in .htaccess nicht möglich). Auf Shared Hosting fragen Sie Ihren Hoster; die meisten tun das bereits.

Apache
# main Apache configuration (not .htaccess), e.g. /etc/apache2/conf-enabled/security.conf
# or /etc/httpd/conf/httpd.conf, then reload Apache
ServerTokens Prod
ServerSignature Off

nginx: im http { }-Block. fastcgi_hide_header entfernt auch den PHP-Versions-Header:

nginx
# in the http { } block of nginx.conf, then: nginx -t && systemctl reload nginx
server_tokens off;
# hides the PHP version header coming from PHP-FPM
fastcgi_hide_header X-Powered-By;

PHP: verhindern, dass PHP X-Powered-By überhaupt sendet. expose_php lässt sich nur in php.ini oder im PHP-FPM-Pool setzen, nicht in .user.ini:

php.ini
; php.ini (or the PHP-FPM pool: php_admin_flag[expose_php] = off), then restart PHP-FPM
expose_php = Off

Ohne Zugriff auf php.ini (Shared Hosting mit Apache oder LiteSpeed) entfernen Sie den Header stattdessen in .htaccess:

Apache (.htaccess)
# .htaccess: removes the PHP version header when you cannot change php.ini
<IfModule mod_headers.c>
  Header always unset X-Powered-By
  Header unset X-Powered-By
</IfModule>
Nach oben

Sicherheits-Header ergänzen

Drei Antwort-Header schließen verbreitete Angriffe im Browser:

Apache (.htaccess)
<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set X-Content-Type-Options "nosniff"
</IfModule>
nginx
# in the server { } block that serves HTTPS
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
Nach oben

Kostenloses Konto erstellen

Überwachen Sie bis zu 5 WordPress-Seiten kostenlos.

Wir senden Ihnen eine Bestätigungs-E-Mail. Mit der Kontoerstellung akzeptieren Sie die Nutzungsbedingungen und die Datenschutzerklärung.