Anleitungen
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.
- Im Adminbereich: Dashboard > Aktualisierungen, dann „Auf Version … aktualisieren“.
- Danach die Website prüfen: Startseite, einige Seiten, das Kontaktformular und, falls vorhanden, den Shop-Checkout.
- Lassen Sie automatische Unterversions-Updates eingeschaltet (Standard). Sie installieren Sicherheitsupdates selbstständig.
Mit Shell-Zugang erledigt WP-CLI dasselbe im Website-Ordner:
wp core update
wp core update-db
Plugins und Themes aktualisieren
Plugins und Themes sind der zweite häufige Angriffsweg: Die meisten WordPress-Sicherheitsmeldungen betreffen sie.
- Unter Plugins > Installierte Plugins und Design > Themes alles aktualisieren, wofür ein Update bereitsteht.
- Löschen Sie nicht genutzte Plugins und Themes. Ein deaktiviertes Plugin liegt weiterhin auf dem Server, und seine Dateien können weiterhin angegriffen werden.
- Premium-Plugins und -Themes erhalten Updates nur mit gültiger Lizenz. Ist sie abgelaufen, verlängern Sie sie oder ersetzen Sie das Plugin.
- Erwägen Sie automatische Updates für Plugins, denen Sie vertrauen („Automatische Aktualisierungen aktivieren“ in der Plugin-Liste).
wp plugin update --all
wp theme update --all
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.
- WP Rocket (kostenpflichtig): läuft auf jedem Server, mit guten Voreinstellungen.
- W3 Total Cache (kostenlos): viele Optionen; wählen Sie beim Seiten-Cache „Disk: Enhanced“.
- WP-Optimize (kostenlos): Seiten-Cache plus Datenbankbereinigung, einfach einzurichten.
- Läuft Ihr Hosting auf dem LiteSpeed-Webserver, nutzen Sie stattdessen LiteSpeed Cache: Es verwendet den eingebauten Cache des Servers. Auf Apache oder nginx ist es eingeschränkt, dort besser eines der Plugins oben.
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 obenKomprimierung 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:
<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>
# 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;
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:
<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>
# 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.
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.
- Aktivieren Sie einen Seiten-Cache (siehe oben). Das behebt es meist schon.
- Ist bereits ein Cache aktiv, suchen Sie ein langsames Plugin: Ein Plugin wie Query Monitor zeigt, welche Plugins Seiten verlangsamen.
- Nutzen Sie eine aktuelle PHP-Version (8.2 oder neuer) mit aktiviertem OPcache.
- Hilft das alles nicht, ist der Hosting-Tarif vermutlich zu klein für die Website.
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:
- Zertifikat für einen anderen Namen: Der Server antwortet mit einem Zertifikat für andere Namen, oft dem Standardzertifikat des Hosters. Die Website hat dort kein eigenes Zertifikat: Richten Sie eines für example.com und www.example.com ein.
- Abgelaufen: Erneuern Sie es. Kostenlose Let’s-Encrypt-Zertifikate gelten 90 Tage und müssen sich automatisch erneuern; ist eines abgelaufen, funktioniert die automatische Erneuerung nicht.
- Selbstsigniert oder nicht vertrauenswürdig: Browser vertrauen nur Zertifikaten einer öffentlichen Zertifizierungsstelle. Ersetzen Sie es durch ein Let’s-Encrypt- (kostenlos) oder kommerzielles Zertifikat und installieren Sie die vollständige Kette („CA-Bundle“ bzw. „fullchain“), nicht nur das Zertifikat selbst.
- Gar kein HTTPS: Installieren Sie ein Zertifikat und leiten Sie http:// danach auf https:// um.
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:
# 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:
# .htaccess, above the # BEGIN WordPress block
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
- Prüfen: Öffnen Sie die Website in einem privaten Fenster mit und ohne www. Es muss ein Schloss erscheinen und keine Warnung.
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.
<Files "xmlrpc.php">
Require all denied
</Files>
location = /xmlrpc.php {
deny all;
}
Auch Sicherheits-Plugins (Wordfence, Solid Security u. a.) können XML-RPC deaktivieren.
Nach obenWordPress-Generator-Tag entfernen
Standardmäßig schreibt WordPress seine genaue Version in jede Seite: <meta name="generator" content="WordPress 7.1.2">. Das verrät Angreifern, welche bekannten Lücken sie probieren können.
Entfernen Sie es mit diesem Code oder mit der Option „WordPress-Version verbergen“ eines Sicherheits-Plugins (Wordfence, Solid Security u. a. haben eine):
// functions.php of a child theme, or a small custom plugin
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
- Fügen Sie den Code in die
functions.phpeines Child-Themes oder in ein kleines eigenes Plugin ein – nicht ins Haupt-Theme, ein Theme-Update würde ihn entfernen. - Leeren Sie danach den Seiten-Cache, sonst erhalten Besucher weiter die alten Seiten mit dem Tag.
- Prüfen: Startseite öffnen, Quelltext anzeigen (Strg+U) und nach „generator“ suchen. Dort darf nicht mehr „WordPress“ stehen.
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):
<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:
location = /readme.html {
deny all;
}
- Prüfen: Öffnen Sie
https://ihre-website/readme.html. Sie sollten „403 Forbidden“ statt der WordPress-Readme-Seite sehen. - Auf Shared Hosting ohne Zugriff auf die Serverkonfiguration nutzen Sie die
.htaccess-Regel; der Dateimanager von cPanel oder Plesk kann sie bearbeiten („versteckte Dateien anzeigen“ aktivieren).
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:
// 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“).
Nach obenVerzeichnisauflistung deaktivieren
Mit aktiver Verzeichnisauflistung kann jeder die Dateiliste eines Ordners wie /wp-content/uploads/ durchsehen, einschließlich privater Uploads und Backups.
Options -Indexes
# nginx lists directories only where autoindex is switched on: remove it, or set
autoindex off;
Ö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
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:
<Files "debug.log">
Require all denied
</Files>
location ~* /debug\.log$ {
deny all;
}
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.
# 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:
# 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 (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:
# .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>
- Prüfen:
curl -I https://ihre-website/sollteServer: nginxoderServer: Apacheohne Nummer zeigen und keine ZeileX-Powered-By: PHP/....
Sicherheits-Header ergänzen
Drei Antwort-Header schließen verbreitete Angriffe im Browser:
Strict-Transport-Security(HSTS): Browser nutzen für Ihre Website immer HTTPS, auch bei eingetipptem oder verlinktem http://. Erst setzen, wenn HTTPS auf der ganzen Website funktioniert.X-Frame-Options: Andere Websites können Ihre Seiten nicht in einem Frame anzeigen (Clickjacking).X-Content-Type-Options: Browser raten keine Dateitypen, was einige Tricks zum Einschleusen von Skripten verhindert.
<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>
# 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;