Wie man die WordPress-Version versteckt und HTTP-Header härtet (Leitfaden 2026)

Wie man die WordPress-Version versteckt und HTTP-Header härtet (Leitfaden 2026)

Zuletzt überprüft: 15. Oktober 2026
16 Min. Lesezeit
Leitfaden
Sicherheitsauditor
In der professionellen Webentwicklung und Systemadministration gehört die Aufklärungsphase (Reconnaissance) zum ersten Schritt jedes Angreifers. Automatisierte Scanner wie WPScan, Nuclei oder Shodan durchforsten täglich Millionen von URLs nach Schwachstellen. Findet ein Scanner eine veraltete WordPress-Kernversion oder ungehärtete HTTP-Antworten, wird die Domain sofort in Ausbeutungs-Warteschlangen eingereiht.

Das Ausblenden von Versionsnummern allein ist zwar reine “Security through Obscurity” und ersetzt niemals regelmäßige Sicherheitsupdates. In Kombination mit robusten HTTP-Sicherheitsheadern, restriktiven Dateiberechtigungen und der Blockierung ungenutzter Schnittstellen reduziert dieser Schritt jedoch das Angriffsrauschen drastisch. In diesem technischen Leitfaden analysieren wir, wie Sie WordPress auf Code- und Webserver-Ebene härten. Den Gesamtüberblick bietet unser Leitfaden zum WordPress-Hardening.

#Warum Generator-Tag und Versionsnummern ein Risiko sind

Standardmäßig gibt eine frische WordPress-Installation an mehreren Stellen bereitwillig Auskunft über ihre genaue Version. Der prominenteste Indikator ist das HTML-Meta-Tag im <head>-Bereich des Dokuments:

<meta name="generator" content="WordPress 6.7.1" />

Automatisierte Crawler benötigen lediglich einen simplen cURL-Aufruf auf die Startseite, um diesen String mit regulären Ausdrücken auszulesen. Neben dem Meta-Generator-Tag exponiert WordPress die Kernversion jedoch noch an weiteren, oft übersehenen Stellen:

  1. RSS- und Atom-Feeds: In /feed/ generiert die Funktion the_generator Tags wie <generator>https://wordpress.org/?v=6.7.1</generator>.
  2. Eingebundene Skripte und Stylesheets: Core-Skripte wie wp-emoji-release.min.js oder Basistreiber werden mit URL-Parametern wie ?ver=6.7.1 ausgeliefert.
  3. Statische Readme-Dateien: Die Datei readme.html im Stammverzeichnis enthält oft Versionsdetails in Reinschrift.

Wenn Sicherheitsforscher eine Remote-Code-Execution (RCE) oder SQL-Injection in einer bestimmten Kern- oder Pluginversion entdecken, suchen Exploit-Netzwerke gezielt nach genau diesen Versionsstrings. Das Entfernen dieser Fußabdrücke nimmt Ihre Website aus dem Raster vollautomatisierter Massen-Scans heraus.

#Generator-Tag und Asset-Parameter per PHP entfernen

Viele Anleitungen empfehlen, einfach den Parameter ver aus allen Asset-URLs per PHP-Filter abzuschneiden. Dies birgt jedoch ein massives Problem für das Browser-Caching: Werden Stylesheets oder Skripte aktualisiert, bemerken Browser und CDNs die Änderung nicht mehr, da die Dateinamen identisch bleiben (Cache-Busting-Versagen).

Die saubere architektonische Lösung besteht darin, Core-Versionsnummern gezielt zu maskieren oder bei eigenen Assets auf Hash-basierte Datei-Fingerprints zu setzen.

#Generator-Tags in Head und Feeds deaktivieren

Fügen Sie die folgenden Hooks in ein Must-Use-Plugin (wp-content/mu-plugins/hardening.php) oder in die functions.php Ihres Child-Themes ein:

<?php
/**
 * Plugin Name: WPPoland Security Hardening
 * Description: Entfernt Versionshinweise und schützt sensible Endpunkte.
 */

// 1. Meta-Generator aus dem HTML-Head entfernen
remove_action('wp_head', 'wp_generator');

// 2. Generator-Tags aus RSS-, Atom- und RDF-Feeds entfernen
remove_action('rss2_head', 'the_generator');
remove_action('rss_head', 'the_generator');
remove_action('rdf_header', 'the_generator');
remove_action('atom_head', 'the_generator');
remove_action('commentsrss2_head', 'the_generator');
remove_action('opml_head', 'the_generator');

// 3. Ausgabe von the_generator leeren (deckt alle Feed-Typen ab)
add_filter('the_generator', '__return_empty_string');

#Parameter ?ver= bei Skripten und Styles gezielt entfernen

Anstatt den Versionsparameter blind zu löschen, prüfen wir, ob der übergebene Wert mit der globalen WordPress-Version übereinstimmt. Falls ja, entfernen wir den Parameter oder ersetzen ihn durch einen anwendungsbezogenen Cache-Schlüssel:

function wppoland_filter_core_asset_versions(string $src): string {
    global $wp_version;

    if (empty($src)) {
        return $src;
    }

    $parts = parse_url($src);
    if (!isset($parts['query'])) {
        return $src;
    }

    parse_str($parts['query'], $query_args);

    if (isset($query_args['ver']) && $query_args['ver'] === $wp_version) {
        // Kernversion aus URL entfernen, um Footprinting zu verhindern
        return remove_query_arg('ver', $src);
    }

    return $src;
}
add_filter('script_loader_src', 'wppoland_filter_core_asset_versions', 9999);
add_filter('style_loader_src', 'wppoland_filter_core_asset_versions', 9999);

Durch diese selektive Methode behalten Plugins wie WooCommerce oder Elementor ihre spezifischen Cache-Busting-Strings, während die Kernversion von WordPress nirgends mehr im Quellcode auftaucht.

#Welche HTTP-Header WordPress neben dem Generator-Tag verraten

Das Generator-Tag ist nicht der einzige Fingerabdruck. Der Core und PHP selbst fügen Antwort-Header hinzu, die einem Scanner die eingesetzte Software verraten, auch wenn im HTML keine Versionsnummer mehr steht.

HeaderGesendet vonWann
Link: <https://beispiel.de/wp-json/>; rel="https://api.w.org/"rest_output_link_header(), seit WordPress 4.4.0bei jeder Frontend-Anfrage, auf template_redirect (Priorität 11)
Link: <https://beispiel.de/?p=123>; rel=shortlinkwp_shortlink_header()auf einzelnen Beiträgen und Seiten, auf template_redirect (Priorität 11)
X-Pingback: https://beispiel.de/xmlrpc.phpWP::send_headers()auf einzelnen Beiträgen, die Pings erlauben (pings_open()), seit 4.4.0 nur noch bedingt
X-Powered-By: PHP/8.3.xPHP selbstsolange expose_php aktiv ist, was der Standardwert ist

Quellen: die Referenz zu rest_output_link_header, die Referenz zu WP::send_headers und die PHP-Dokumentation der Core-Direktiven.

Die WordPress-Header lassen sich mit denselben Hooks entfernen, die sie hinzufügen. X-Pingback läuft durch den Filter wp_headers, der die HTTP-Header filtert, bevor sie an den Browser gehen. Ergänzen Sie das Must-Use-Plugin:

// REST-API-Discovery: Link-Header und <link>-Tag
remove_action('template_redirect', 'rest_output_link_header', 11);
remove_action('wp_head', 'rest_output_link_wp_head', 10);

// Shortlink: Header und <link>-Tag
remove_action('template_redirect', 'wp_shortlink_header', 11);
remove_action('wp_head', 'wp_shortlink_wp_head', 10);

// X-Pingback auf Einzelbeiträgen
add_filter('wp_headers', function (array $headers): array {
    unset($headers['X-Pingback']);
    return $headers;
});

Das Entfernen des Discovery-Links schaltet die REST API nicht ab: /wp-json/ antwortet weiterhin, und der Block-Editor ist darauf angewiesen. Ziel ist, das CMS nicht in jeder HTML-Antwort anzukündigen, nicht die API zu brechen.

X-Powered-By lässt sich nicht aus WordPress heraus ändern. Das PHP-Handbuch führt expose_php als Einstellung, die nur in der php.ini gesetzt werden kann, ini_set() bleibt also wirkungslos. Setzen Sie den Wert im PHP-FPM-Pool oder in der zentralen php.ini:

expose_php = Off

Das OWASP HTTP Headers Cheat Sheet empfiehlt, alle X-Powered-By-Header zu entfernen und den Server-Header zu streichen oder mit einem nichtssagenden Wert zu belegen. Es weist auch darauf hin, dass Angreifer den Technologie-Stack auf anderen Wegen erkennen können. Das ist die ehrliche Grenze dieses ganzen Abschnitts.

#HTTP-Security-Header auf dem Webserver setzen

HTTP-Header sind Anweisungen, die der Webserver bei jeder Antwort an den Webbrowser des Besuchers mitsendet. Sie steuern moderne Sicherheitsmechanismen im Browser, die vor Cross-Site-Scripting (XSS), Clickjacking, MIME-Type-Sniffing und Downgrade-Attacken schützen.

Da PHP-basierte Header-Modifikationen bei statischem Caching (z.B. Nginx FastCGI-Cache, Varnish oder Cloudflare) oft umgangen werden, sollten Security-Header grundsätzlich direkt im Webserver (Apache .htaccess oder Nginx Server-Block) konfiguriert werden.

#1. X-Content-Type-Options: nosniff

Verhindert, dass Browser versuchen, den MIME-Typ einer Datei eigenständig zu erraten (MIME-Sniffing). Dadurch wird verhindert, dass eine manipulierte Bilddatei plötzlich als bösartiges JavaScript ausgeführt wird.

Header always set X-Content-Type-Options "nosniff"

#2. X-Frame-Options: SAMEORIGIN

Schützt Ihre Website vor Clickjacking-Angriffen. Clickjacking tritt auf, wenn ein Angreifer Ihre Website in einem unsichtbaren <iframe> über eine betrügerische Seite legt und den Benutzer dazu bringt, unwissentlich auf administrative Buttons zu klicken.

Header always set X-Frame-Options "SAMEORIGIN"

#3. HTTP Strict Transport Security (HSTS)

HSTS zwingt kompatible Browser, die Verbindung zu Ihrer Domain ausschließlich über HTTPS aufzubauen, selbst wenn der Nutzer manuell http:// in die Adresszeile eingibt. Nach dem ersten sicheren Besuch unterbindet das SSL-Strip-Angriffe im lokalen Netzwerk. Die Lücke beim allerersten Besuch schließt nur die Preload-Liste (siehe unten).

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

#4. Referrer-Policy: strict-origin-when-cross-origin

Steuert, wie viele Referrer-Informationen an externe Websites übermittelt werden, wenn Nutzer externen Links folgen. Diese Einstellung schützt sensible Parameter in URLs interner Arbeitsabläufe vor dem Abfluss an Drittanbieter.

Header always set Referrer-Policy "strict-origin-when-cross-origin"

#5. Permissions-Policy

Deaktiviert riskante Browser-APIs (Kamera, Mikrofon, Geolocation), sofern diese auf Ihrer Website nicht benötigt werden. Selbst im Falle einer XSS-Lücke kann ein Schadskript diese Geräteschnittstellen nicht ansprechen.

Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=()"

#6. Content-Security-Policy (CSP)

Die Content Security Policy ist das mächtigste Werkzeug moderner Websicherheit. Sie legt fest, aus welchen Quellen Skripte, Styles, Bilder und Schriften geladen werden dürfen. Fehlt 'unsafe-inline' in script-src, blockiert der Browser Inline-Skripte ohne Nonce oder Hash.

Da viele WordPress-Plugins Inline-JavaScript nutzen, erfordert eine strikte CSP sorgfältiges Testen. Beginnen Sie mit einem auditierbaren Basis-Set:

Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com; object-src 'none'; frame-ancestors 'self';"

#7. X-XSS-Protection im Jahr 2026

Ältere Anleitungen empfahlen X-XSS-Protection: 1; mode=block. Diese Empfehlung ist überholt. Die MDN-Seite zu X-XSS-Protection führt den Header als veraltet und nicht standardisiert, empfiehlt stattdessen eine Content-Security-Policy und warnt, dass der Filter in manchen Fällen XSS-Lücken in sonst sicheren Websites erzeugen kann. Laut MDN kann mode=block zudem für Seitenkanalangriffe anfällig sein, wenn die Seite in einem <iframe> eingebettet werden kann.

OWASP kommt zum selben Ergebnis: den Header nicht senden oder ausdrücklich auf 0 setzen. Wenn eine alte Serverkonfiguration oder ein Plugin bereits 1; mode=block sendet, überschreiben Sie den Wert:

Header always set X-XSS-Protection "0"
add_header X-XSS-Protection "0" always;

Den Schutz, um den es eigentlich geht, liefert eine CSP ohne 'unsafe-inline' für Skripte. Auf den meisten WordPress-Websites ist das ein längeres Projekt.

#Sendet WordPress X-Frame-Options von selbst

Ja, auf zwei Bildschirmen. Der Core hängt send_frame_options_header() an die Aktion login_init und in wp-admin/includes/admin-filters.php an admin_init. Die Funktion, verfügbar seit WordPress 3.1.3, sendet zwei Header, solange noch keine Header gesendet wurden:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self';

Quelle: die Referenz zu send_frame_options_header.

wp-login.php und das Dashboard sind damit auch ohne Serverkonfiguration gegen Framing geschützt. Das öffentliche Frontend nicht: Themes, Checkout-Seiten und Kontaktformulare bekommen diesen Header nur, wenn Sie ihn selbst setzen.

Für Apache hat das eine praktische Folge. Laut mod_headers-Dokumentation ersetzt set jeden vorhandenen Header gleichen Namens, während append den neuen Wert kommagetrennt an den vorhandenen anhängt. Mit Header append trägt die Login-Seite den Wert von WordPress plus Ihren eigenen, mit Header always set genau einen. Dieselbe Dokumentation erklärt, dass Header, die PHP hinter mod_proxy_fcgi erzeugt, in der always-Tabelle liegen. Deshalb verwenden alle Beispiele hier Header always set. MDN ergänzt, dass die Direktive ALLOW-FROM obsolet ist und moderne Browser den gesamten Header ignorieren lässt, und dass X-Frame-Options in einem <meta>-Tag keinerlei Wirkung hat.

#Apache- und Nginx-Konfiguration zum Verstecken der WordPress-Version

Hier finden Sie einsatzbereite Konfigurationsblöcke für die beiden am weitesten verbreiteten Webserver.

#Vollständige .htaccess-Konfiguration für Apache

Fügen Sie diesen Block an den Anfang Ihrer .htaccess-Datei ein, idealerweise vor den Rewrite-Regeln von WordPress:

<IfModule mod_headers.c>
    # Verhindert MIME-Sniffing
    Header always set X-Content-Type-Options "nosniff"

    # Clickjacking-Schutz
    Header always set X-Frame-Options "SAMEORIGIN"

    # HTTPS erzwingen (1 Jahr Laufzeit inkl. Subdomains)
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    # Referrer-Kontrolle
    Header always set Referrer-Policy "strict-origin-when-cross-origin"

    # Hardware-APIs sperren
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"

    # Veralteter XSS-Filter: ausdrücklich abgeschaltet (siehe Abschnitt X-XSS-Protection)
    Header always set X-XSS-Protection "0"
</IfModule>

# Zugriff auf sensible Dateien sperren
<FilesMatch "^(wp-config\.php|readme\.html|license\.txt|\.env)">
    Require all denied
</FilesMatch>

# XML-RPC Schnittstelle deaktivieren
<Files xmlrpc.php>
    Require all denied
</Files>

#Vollständiger Nginx-Server-Block (nginx.conf)

Unter Nginx werden Header innerhalb der server-Direktive deklariert. Ein location-Block mit eigenen add_header-Zeilen erbt diese Header nicht (Details im nächsten Abschnitt):

server {
    listen 443 ssl http2;
    server_name beispiel.de www.beispiel.de;

    # SSL-Zertifikate
    ssl_certificate /etc/letsencrypt/live/beispiel.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/beispiel.de/privkey.pem;

    # Security Headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # Server-Tokens ausblenden (versteckt Nginx-Version)
    server_tokens off;

    root /var/www/beispiel.de;
    index index.php index.html;

    # Sensible Core-Dateien blockieren
    location ~* /(?:readme\.html|license\.txt|wp-config\.php) {
        deny all;
    }

    # XML-RPC sperren
    location = /xmlrpc.php {
        deny all;
        access_log off;
        log_not_found off;
    }

    # PHP-Verarbeitung
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

#Warum Nginx-Security-Header in location-Blöcken verschwinden

Eine häufige Überraschung nach einer “funktionierenden” Konfiguration: curl -I auf die Startseite zeigt alle Security-Header, eine CSS-Datei oder wp-login.php dagegen keinen einzigen. Die Dokumentation des Moduls ngx_http_headers_module nennt den Grund: add_header-Direktiven werden genau dann von der vorherigen Konfigurationsebene geerbt, wenn auf der aktuellen Ebene keine eigene add_header-Direktive steht.

Ein einziges add_header Cache-Control ... in location ~* \.(css|js)$ reicht, damit dieser Block alle Security-Header verliert. Zwei Auswege:

  1. Die Header in ein Snippet auslagern und es im server-Block sowie in jedem location einbinden, das eigene add_header-Zeilen hat:
# /etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
location ~* \.(css|js|woff2)$ {
    include snippets/security-headers.conf;
    add_header Cache-Control "public, max-age=31536000, immutable";
}
  1. Ab Nginx 1.29.3 hängt add_header_inherit merge; die Werte der vorherigen Ebene an die der aktuellen an, statt sie zu ersetzen.

Auch der Parameter always zählt. Ohne ihn setzt Nginx den Header nur bei den Statuscodes 200, 201, 204, 206, 301, 302, 303, 304, 307 und 308. Ein 403 auf /xmlrpc.php oder eine 404-Seite geht ohne Header hinaus.

#Welche Voraussetzungen die HSTS-Preload-Liste hat

Das Token preload in den Beispielen oben wirkt nicht von selbst. Es ist ein Antrag, die Domain fest in Browser einzutragen, und hstspreload.org nennt die Bedingungen für die Einreichung:

  • ein gültiges Zertifikat ausliefern,
  • auf demselben Host von HTTP auf HTTPS umleiten, falls Port 80 offen ist,
  • alle Subdomains über HTTPS ausliefern, auch www, wenn dafür ein DNS-Eintrag existiert,
  • den HSTS-Header auf der Basisdomain über HTTPS senden, mit max-age von mindestens 31536000 Sekunden, includeSubDomains und preload,
  • leitet die HTTPS-Seite weiter um, muss schon diese Umleitung den HSTS-Header tragen.

Dieselbe Seite warnt, dass Preloading für alle Subdomains gilt, auch für interne, die nicht öffentlich erreichbar sind, dass neue Einträge mehrere Monate bis zur stabilen Chrome-Version brauchen können und dass das Entfernen aus der Liste in der Regel langsam und mühsam ist. Ein Staging-System auf einer Subdomain ohne Zertifikat lädt dann in keinem Browser mehr, der die Liste kennt.

OWASP empfiehlt max-age=63072000; includeSubDomains; preload, also zwei Jahre. Die Reihenfolge, die Aussperrungen vermeidet: mit kurzem max-age ohne preload beginnen, alle Subdomains prüfen, den Wert erhöhen und erst einreichen, wenn die gesamte Domain dauerhaft nur noch HTTPS spricht. Wer diese Verpflichtung noch nicht eingehen will, lässt preload aus dem Header weg.

#XML-RPC und User Enumeration in WordPress sperren

Neben dem einfachen Verstecken der WordPress-Version gibt es zwei kritische Standard-Endpunkte, über die Angreifer wertvolle Informationen über Ihr System gewinnen:

#1. XML-RPC (xmlrpc.php)

Die XML-RPC-Schnittstelle stammt aus der Frühzeit von WordPress, als Drittanbieter-Anwendungen noch keine moderne REST API hatten. Heute wird sie häufig für Pingback-DDoS-Attacken und von Brute-Force-Bots missbraucht. Über den Methodenaufruf system.multicall kann ein Angreifer hunderte Passwortkombinationen in einer einzigen HTTP-Anfrage ausprobieren und so typische Login-Limiter umgehen.

Sofern Sie nicht die offizielle mobile WordPress-App oder das Jetpack-Plugin nutzen, sollte xmlrpc.php ausnahmslos auf Serverebene blockiert werden (siehe Webserver-Regeln oben).

Verlassen Sie sich nicht auf add_filter('xmlrpc_enabled', '__return_false'); als vollständige Sperre. Die Referenz zum Hook xmlrpc_enabled stellt klar, dass der Filter entgegen seinem Namen nur XML-RPC-Methoden steuert, die eine Authentifizierung erfordern. Pingbacks und andere Endpunkte ohne Login bleiben offen. Nur die Webserver-Regel stoppt die Anfrage, bevor PHP läuft.

#2. User Enumeration über die REST API

Standardmäßig liefert der WordPress-Endpunkt /wp-json/wp/v2/users eine Liste aller Benutzer aus, die Beiträge verfasst haben, inklusive numerischer ID und Slug, der oft dem Anmeldenamen entspricht. Damit fehlt Angreifern nur noch das Passwort.

Um das unbefugte Auslesen von Benutzerdaten zu stoppen, fügen Sie folgenden Hook in Ihr Hardening-Plugin ein. Die Prüfung auf list_users ist wichtig: Ohne sie verlieren Redakteure im Block-Editor die Autorenauswahl, die denselben Endpunkt liest.

add_filter('rest_endpoints', function(array $endpoints): array {
    if (isset($endpoints['/wp/v2/users']) && !current_user_can('list_users')) {
        unset($endpoints['/wp/v2/users']);
    }
    if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)']) && !current_user_can('list_users')) {
        unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
    }
    return $endpoints;
});

Zusätzlich sollte die traditionelle Abfrage via /?author=1 abgefangen und auf die Startseite umgeleitet werden:

add_action('template_redirect', function(): void {
    if (is_author() && !is_admin()) {
        wp_safe_redirect(home_url(), 301);
        exit;
    }
});

#Wie man die WordPress-Loginseite absichert

Die Endpunkte /wp-admin/ und /wp-login.php stehen unter Dauerbeschuss. So schützen Sie sie.

#1. Login-Versuche begrenzen

Standardmäßig erlaubt WordPress unbegrenzt viele Login-Versuche.

Plugin: Limit Login Attempts Reloaded oder fail2ban auf dem Server, das fehlgeschlagene Logins aus dem Access-Log liest. Eine minimale Version als mu-plugin muss sich in den Filter authenticate einhängen, sonst blockiert der Zähler nie etwas:

add_filter('authenticate', function ($user) {
    $key = 'wppoland_login_fail_' . md5($_SERVER['REMOTE_ADDR'] ?? '');
    if ((int) get_transient($key) >= 5) {
        return new WP_Error('too_many_attempts', 'Too many failed logins. Try again in 15 minutes.');
    }
    return $user;
}, 30);

add_action('wp_login_failed', function () {
    $key = 'wppoland_login_fail_' . md5($_SERVER['REMOTE_ADDR'] ?? '');
    set_transient($key, (int) get_transient($key) + 1, 15 * MINUTE_IN_SECONDS);
});

Hinter einem Reverse Proxy oder CDN ist REMOTE_ADDR die Adresse des Proxys, dieser Code würde also alle gleichzeitig aussperren. Konfigurieren Sie zuerst die echte Client-IP im Webserver.

#2. Zwei-Faktor-Authentifizierung (2FA)

Selbst wenn ein Angreifer Ihr Passwort erbeutet, kommt er ohne den zweiten Faktor nicht hinein.

Plugins:

#3. Login-URL ändern

Standardmäßig weiß jeder, dass der Login unter /wp-login.php liegt.

Plugin: WPS Hide Login

Das Plugin ändert die URL auf etwas wie /mein-geheimer-login/. Bots, die nach /wp-login.php suchen, bekommen eine 404. Das ist wieder Verschleierung: weniger Rauschen im Log, aber kein Ersatz für 2FA. Die Sperre der User Enumeration steht im vorigen Abschnitt.

#Wie man die WordPress-Datenbank absichert

#Tabellenpräfix ändern

Standardmäßig verwendet WordPress wp_ als Tabellenpräfix. Ein eigenes Präfix verhindert keine SQL-Injection; es lässt nur automatisierte Skripte scheitern, die wp_-Tabellennamen fest im Code haben.

Bei der Installation: ein eigenes Präfix wie xyz_ oder prod_ setzen.

Nach der Installation: ein Plugin wie Change Table Prefix nutzen oder Tabellen sowie die präfixierten Schlüssel in options und usermeta von Hand umbenennen (vorher ein Backup anlegen).

Das Abschalten des Datei-Editors (DISALLOW_FILE_EDIT) und die Rechte für wp-config.php behandelt der nächste Abschnitt.

#Welche Dateiberechtigungen WordPress braucht

Eine häufige Ursache für erfolgreiche Manipulationen nach einem Einbruch sind übermäßig liberale Dateirechte. WordPress benötigt lediglich im Verzeichnis wp-content/uploads/ Schreibrechte, um Mediendateien hochzuladen. Sämtliche PHP-Dateien des Cores, der Themes und der Plugins sollten für den Webserver-Prozess schreibgeschützt sein.

#Empfohlenes Rechtemodell auf Linux-Servern

Verbinden Sie sich per SSH mit Ihrem Server und setzen Sie die Standardberechtigungen:

# Verzeichnisse auf 755 (rwxr-xr-x) setzen
find /var/www/beispiel.de/ -type d -exec chmod 755 {} \;

# Dateien auf 644 (rw-r--r--) setzen
find /var/www/beispiel.de/ -type f -exec chmod 644 {} \;

# Konfigurationsdatei restriktiv schützen
chmod 400 /var/www/beispiel.de/wp-config.php

Wenn Ihr Webserver unter einem dedizierten Benutzer läuft (z.B. www-data), stellen Sie sicher, dass der Besitzer der Dateien ein unprivilegierter Systembenutzer ist und der Webserver-Benutzer nur der Gruppe angehört.

#Dateibearbeitung im WordPress-Dashboard deaktivieren

Verhindern Sie, dass Administratoren oder kompromittierte Admin-Accounts PHP-Dateien direkt im WordPress-Backend editieren können. Fügen Sie folgende Zeile in die wp-config.php vor /* That's all, stop editing! */ ein:

define('DISALLOW_FILE_EDIT', true);

Damit wird der integrierte Theme- und Plugin-Editor im Dashboard komplett deaktiviert.

#WordPress-Härtung mit curl prüfen

Nach der Implementierung müssen die Maßnahmen auf Funktionstüchtigkeit geprüft werden, um Fehlkonfigurationen auszuschließen.

  1. HTTP-Header im Terminal prüfen:
    curl -sI https://beispiel.de | grep -iE "(strict-transport|x-frame|x-content|referrer|permissions|content-security)"
  2. Versionshinweise im Quelltext suchen: Laden Sie die Startseite herunter und suchen Sie nach Versionsresten:
    curl -s https://beispiel.de | grep -i "generator"
    curl -sI https://beispiel.de | grep -iE "x-powered-by|x-pingback|api.w.org"
  3. Login-Seite mit genau einem X-Frame-Options:
    curl -sI https://beispiel.de/wp-login.php | grep -i x-frame
  4. Gesperrte Dateien müssen 403 liefern:
    curl -sI https://beispiel.de/xmlrpc.php | head -1
    curl -sI https://beispiel.de/readme.html | head -1
    Wiederholen Sie die Header-Prüfung auf einer URL, die 404 liefert. Unter Nginx ohne always fehlen die Header genau dort.
  5. Automatisierte Audit-Tools: Nutzen Sie unabhängige Analyse-Plattformen wie SecurityHeaders.com, um eine A- oder A+-Bewertung Ihrer HTTP-Header zu bestätigen.

#Fazit und nächste Schritte

Gezieltes WordPress-Hardening erfordert keine teuren All-in-One-Sicherheitsplugins, die das System oft nur verlangsamen. Durch saubere PHP-Filter zur Versionsmaskierung, robuste HTTP-Sicherheitsheader auf Webserver-Ebene und restriktive Dateiberechtigungen minimieren Sie Ihre Angriffsfläche nachhaltig.

Möchten Sie Ihre WordPress-Infrastruktur professionell absichern und auf Konformität prüfen lassen? Informieren Sie sich über unsere spezialisierten WordPress-Sicherheitsaudits oder lassen Sie Ihre Umgebung durch erfahrene WordPress-Entwickler optimieren.

Nächster Schritt

Machen Sie aus dem Artikel eine echte Umsetzung

Dieser Block stärkt die interne Verlinkung und führt Nutzer gezielt zum nächsten sinnvollen Schritt im Service- und Content-System.

Soll das Thema auf Ihrer Website umgesetzt werden?

Ich kann daraus ein konkretes Audit, Hardening-Maßnahmen und einen priorisierten Fix-Plan ableiten.

Häufig gestellte Fragen

Praktische Antworten zur Umsetzung des Themas.

Reicht es aus, die WordPress-Versionsnummer zu verstecken#
Nein. Das Verstecken der Versionsnummer wirkt gegen automatisierte Massenscanner, ersetzt aber keine echten Schutzmaßnahmen. Updates von Core und Plugins, Zwei-Faktor-Authentifizierung und restriktive Zugriffsrechte bleiben nötig.
Verhindert das Entfernen des ver-Parameters das Browser-Caching#
Wird der Parameter pauschal aus allen CSS- und JS-Dateien entfernt, erkennen Browser Änderungen nicht mehr zuverlässig. Der Filter in diesem Leitfaden entfernt deshalb nur die Core-Version und lässt Plugin- und Theme-Versionen intakt.
Warum XML-RPC in WordPress deaktivieren#
xmlrpc.php wird häufig für Brute-Force-Angriffe per system.multicall missbraucht. Ohne Jetpack oder die mobile WordPress-App fehlt nach der Sperre nichts. Sperren Sie die Datei im Webserver, denn der Filter xmlrpc_enabled lässt Pingbacks offen.
Kann eine zu strikte Content Security Policy die Website zerstören#
Ja. Fehlen legitime Skripte von Drittanbietern oder nötige Inline-Styles in der CSP, blockiert der Browser sie. Testen Sie neue Richtlinien vorab mit dem Header Content-Security-Policy-Report-Only.
Sollte man X-XSS-Protection noch senden#
Nein. MDN führt den Header als veraltet, OWASP rät, ihn nicht zu senden oder auf 0 zu setzen. Schutz vor XSS kommt aus einer Content Security Policy.

Sie brauchen ein FAQ für Branche und Zielmarkt? Ich erstelle eine Version passend zu Ihren Business-Zielen.

Kontakt aufnehmen

Ähnliche Artikel