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.

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

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

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">. 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):

PHP
// functions.php of a child theme, or a small custom plugin
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

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“).

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.