Anleitung

PHP-Version in HTTP-Response-Headern verstecken

19. April 2026

Öffne auf einer beliebigen PHP-Seite den Netzwerk-Tab im Browser und schau dir die Response-Header des HTML-Dokuments an. Auf vielen Installationen findest du eine Zeile wie X-Powered-By: PHP/8.1.27. Das ist PHP, das dem ganzen Internet bereitwillig verrät, welche Version da gerade läuft. Ein kleines Detail, das den meisten Seitenbetreibern nie auffällt, aber genau diese Zeile gehört zu den einfachsten Infos, die ein Angreifer einsammelt, bevor er entscheidet, ob eine Seite es wert ist, näher untersucht zu werden.

Diese Anleitung erklärt, warum es den Header überhaupt gibt, was er konkret verrät und wie du ihn auf jedem realistischen Weg loswirst. Von der Server-Config bis runter zu einer einzelnen WordPress-Seite auf einem Shared-Host, bei dem du die php.ini nicht anfassen darfst.

Was der Header einem Angreifer tatsächlich verrät

Ein Wert wie PHP/8.1.27 liefert zwei nützliche Infos. Erstens die Major-Version. Wenn du noch auf PHP 7.4 oder 8.0 unterwegs bist, beide Versionen sind längst am End-of-Life, weiß ein Angreifer sofort, dass keine Sicherheits-Patches mehr kommen. Zweitens den exakten Patchlevel. Zwischen zwei Patch-Releases stehen meistens ein paar öffentlich bekannte CVEs. Ein Scanner gleicht deine gemeldete Version in Millisekunden mit seiner CVE-Datenbank ab und entscheidet, ob er weiter draufhält oder weiterzieht.

Die Version zu verstecken patcht natürlich nichts. Wenn dein PHP veraltet ist, ist es danach genauso veraltet. Aber den Broadcast abzuschalten bringt drei konkrete Effekte: automatisierte Scanner stufen deine Seite nicht mehr als einfaches Ziel ein, manuelle Aufklärung wird ein kleines bisschen teurer, und eine Information, die schlicht nichts in deinen Response-Headern zu suchen hat, verschwindet. Es gibt keinen legitimen Grund, warum das öffentliche Internet deinen PHP-Patchlevel wissen müsste.

Manche Compliance-Rahmen und Security-Checklisten (PCI-DSS, BSI IT-Grundschutz, interne Audits in größeren Unternehmen) fordern die Unterdrückung dieses Headers sogar explizit. Auch wenn du in keinem dieser Kontexte unterwegs bist, ist es ein Fünf-Minuten-Fix ohne jeden Nachteil.

Woher kommt der Header eigentlich?

Den X-Powered-By-Header setzt PHP selbst, nicht dein Webserver. Das Verhalten steuert eine einzige php.ini-Direktive namens expose_php. Steht diese auf On (Default in den meisten PHP-Distributionen), hängt PHP den Header automatisch an jede Antwort. Schaltest du sie aus, verschwindet der Header für jede Seite auf dieser PHP-Installation.

Ergänzend setzen manche Frameworks oder Applikationen ihren eigenen X-Powered-By-Wert. Express, ASP.NET, einige mehr. WordPress selbst tut das nicht, aber Plugins gelegentlich schon. Nachdem du also PHPs Versions-Ansage abgeschaltet hast, lohnt ein erneuter Blick in die Response-Header, ob da vielleicht noch etwas anderes leakt.

Option 1: expose_php in der php.ini abschalten

Das ist die sauberste Lösung, sofern du den Server kontrollierst. Öffne deine php.ini. Unter Linux liegt sie meistens in einem dieser Pfade:

  • /etc/php/8.3/fpm/php.ini für PHP-FPM, das gängigste Setup hinter nginx und modernem Apache
  • /etc/php/8.3/apache2/php.ini für Apache mit mod_php
  • /etc/php/8.3/cli/php.ini für die Kommandozeile, in diesem Kontext meistens irrelevant

Die Versionsnummer im Pfad passt zur PHP-Version, die bei dir läuft. Such die Zeile:

expose_php = On

Und änder sie zu:

expose_php = Off

Datei speichern, PHP-Prozess neu starten, damit die Änderung greift. Für PHP-FPM sieht das so aus:

sudo systemctl restart php8.3-fpm

Für Apache mit mod_php:

sudo systemctl restart apache2

Zum Prüfen ein kurzes curl gegen deine Seite und ein Blick auf die Header:

curl -I https://deinedomain.de

Die X-Powered-By-Zeile sollte komplett weg sein. Falls sie noch da ist, hast du in der falschen php.ini gelandet (davon gibt es oft mehrere). Prüf mit php --ini auf der Kommandozeile, welche tatsächlich geladen wird. Oder kurz mit einem phpinfo()-Aufruf auf der Seite (und den danach bitte sofort wieder entfernen).

Option 2: expose_php per .user.ini setzen

Nicht jeder hat Zugriff auf die globale php.ini. Auf vielen Shared-Hostern kannst du PHP-Einstellungen aber über eine Datei namens .user.ini beeinflussen, die du im Document-Root deiner Seite ablegst. Leg eine .user.ini mit diesem Inhalt an oder bearbeite sie:

expose_php = Off

PHP liest diese Datei bei jedem Request, cacht das Ergebnis aber standardmäßig einige Minuten (gesteuert durch user_ini.cache_ttl, meistens 300 Sekunden). Also fünf Minuten warten, eventuelle Edge-Caches leeren, und dann die Header nochmal prüfen.

Ein Hinweis zur Vorsicht: Nicht jeder Host erlaubt, expose_php über .user.ini zu überschreiben. Die Direktive hat den Modus PHP_INI_PERDIR, was es technisch zuließe, aber manche Hoster whitelisten explizit, was geändert werden darf. Wenn die Einstellung nach ein paar Minuten keine Wirkung zeigt, blockiert dein Host das wahrscheinlich. Spring zu Option 5.

Option 3: Den Header in Apache entfernen

Den Header kannst du auch auf Webserver-Ebene rausziehen, unabhängig davon, was PHP intern macht. In deiner Apache-Konfiguration (oder in der .htaccess der Seite, wenn AllowOverride es zulässt) trägst du ein:

<IfModule mod_headers.c>
  Header unset X-Powered-By
  Header always unset X-Powered-By
</IfModule>

Warum sowohl unset als auch always unset? Die reguläre Variante greift nur bei erfolgreichen 2xx-Antworten. Die always-Variante greift zusätzlich bei Fehlerantworten wie 500-Seiten. Genau bei denen willst du die Version ja erst recht nicht leaken.

Damit das funktioniert, muss mod_headers aktiv sein. Unter Debian und Ubuntu:

sudo a2enmod headers
sudo systemctl reload apache2

Wenn du im Shared Hosting mit Apache unterwegs bist, funktioniert die .htaccess-Variante fast immer, weil die meisten Hoster mod_headers aktiviert haben und es per AllowOverride All zulassen. Wirkt die Regel nicht, frag deinen Hoster, ob mod_headers verfügbar ist.

Option 4: Den Header in nginx entfernen

Das Äquivalent unter nginx kommt in den server-Block deiner Seite, oder in einen allgemeinen http-Block, wenn du es seitenübergreifend willst:

more_clear_headers 'X-Powered-By';

Kleiner Haken: more_clear_headers braucht das headers-more-nginx-module, das nicht in jedem Standard-nginx-Paket enthalten ist. Debian und Ubuntu liefern es im Paket nginx-extras mit:

sudo apt install nginx-extras

Bist du auf einem nackten nginx ohne das Extras-Modul unterwegs, gibt es einen Workaround über fastcgi_hide_header. Das ist sogar das passendere Werkzeug, wenn der Header von PHP-FPM kommt (und das tut er in den meisten Fällen):

location ~ \.php$ {
    # dein übliches fastcgi_pass und die Params
    fastcgi_hide_header X-Powered-By;
}

Nginx mit sudo nginx -t && sudo systemctl reload nginx neu laden und mit curl -I prüfen.

Option 5: Sonderfall WordPress

WordPress selbst setzt den X-Powered-By-Header nicht. Er kommt von PHP. Alles oben Beschriebene gilt also genauso: expose_php ausschalten oder den Header in Apache oder nginx strippen löst das Problem auch für eine WordPress-Seite. Aber WordPress bietet dir zwei zusätzliche Wege, die du kennen solltest.

Den Header aus WordPress heraus unterdrücken. Wenn du keinen Zugriff auf die Webserver-Config hast und die .user.ini auf deinem Host nicht greift, kannst du den Header programmatisch über ein Must-Use-Plugin rauswerfen. Leg eine Datei unter wp-content/mu-plugins/remove-powered-by.php an (den Ordner mu-plugins ggf. vorher erstellen) und packe das rein:

<?php
/**
 * Plugin Name: Remove X-Powered-By
 * Description: Entfernt den X-Powered-By-Header aus jeder WordPress-Antwort.
 */

if (!defined('ABSPATH')) {
    exit;
}

add_action('send_headers', function () {
    if (function_exists('header_remove')) {
        header_remove('X-Powered-By');
    }
});

Zwei Dinge, die du bei diesem Ansatz im Kopf behalten solltest. Erstens funktioniert header_remove() nur, solange PHP den Response-Body noch nicht zu senden begonnen hat. Der Hook send_headers feuert bei jeder normalen WordPress-Seite genau zum richtigen Zeitpunkt, das greift also für Posts, Pages, das Admin-Backend und die REST-API. Bei Requests, die nie bis zu WordPress kommen (direkte Zugriffe auf statische Dateien, gecachte Seiten aus einem vorgelagerten Proxy), hilft es nicht. Zweitens setzen manche Hoster einen Reverse-Proxy davor (Cloudflare, Varnish, nginx vor Apache), der den Header aus der Backend-Antwort einfach weiterreicht. Wenn der Header nach Installation des Plugins im Browser noch auftaucht, cacht oder kopiert der Proxy ihn. Cache leeren, und idealerweise den Header auch auf Proxy-Ebene entfernen.

WordPress verrät seine eigene Version auch noch. Wenn du schon dabei bist, kümmerst du dich am besten direkt um das <meta name="generator" content="WordPress X.Y.Z">-Tag, das WordPress in den HTML-Head schreibt, und um die ?ver=X.Y.Z-Parameter an CSS- und JS-Dateien. Das ist zwar nicht derselbe Header wie die PHP-Version, leakt aber die exakte WordPress-Version, was für einen Angreifer vergleichbar nützlich ist. Das folgende Snippet erledigt beides:

<?php
// Generator-Meta-Tag entfernen
remove_action('wp_head', 'wp_generator');

// WordPress-Version aus RSS-Feeds, Backend usw. entfernen
add_filter('the_generator', '__return_empty_string');

// ?ver=X.Y.Z von CSS- und JS-URLs abstreifen
add_filter('style_loader_src', function ($src) {
    return remove_query_arg('ver', $src);
}, 9999);

add_filter('script_loader_src', function ($src) {
    return remove_query_arg('ver', $src);
}, 9999);

Beachte, dass das Entfernen des ?ver=-Parameters einen Nebeneffekt hat: Der Browser erkennt allein anhand der URL nicht mehr, wann sich eine CSS- oder JS-Datei geändert hat. Wenn du auf genau diesen Mechanismus zum Cache-Busting setzt, lass den Filter weg oder ersetz ver durch einen Hash des Dateiinhalts.

Option 6: Cloudflare, Sucuri und andere Reverse-Proxies

Sitzt du hinter Cloudflare oder einem vergleichbaren CDN, kannst du den Header noch eine Ebene weiter außen strippen. Praktisch, weil das alle Seiten einer Zone auf einen Schlag mitnimmt, ohne dass du am Origin etwas anfassen musst. In Cloudflare geht das über eine Transform Rule vom Typ "Modify Response Header":

  • Regelname: "Remove X-Powered-By"
  • When incoming requests match: Hostname equals deinedomain.de (oder mit Wildcard für die ganze Zone)
  • Then: Remove header X-Powered-By

Sucuri, BunnyCDN, Fastly und die meisten anderen Reverse-Proxies bieten ein Äquivalent. Die genaue Bezeichnung im Dashboard variiert, gesucht ist immer "Response-Header modifizieren" oder "Edge Rules".

Weitere Header, die du gleich mitprüfen solltest

PHP ist nicht das Einzige, was gern laut von sich erzählt. Wenn du schon curl offen hast, schau die Response-Header nach folgenden Kandidaten ab:

  • Server: Apache/2.4.58 (Ubuntu) verrät Apaches Patchlevel und deine OS-Variante. Unterdrücken mit ServerTokens Prod und ServerSignature Off in Apache, oder server_tokens off in nginx.
  • X-Powered-CMS, X-Generator, X-Drupal-Cache oder ähnliche. Meist von Plugins oder CMS-spezifischen Modulen gesetzt. Entfernen analog zu X-Powered-By.
  • X-AspNet-Version oder X-AspNetMvc-Version. Nur relevant, wenn irgendwo in deinem Stack .NET mitspielt, aber gut zu wissen.

InspectWP markiert all diese Header im Sicherheits-Bereich deines Reports. Nachdem du die PHP-Version versteckt hast, ist ein frischer Scan der schnellste Weg herauszufinden, welche kleinen Versehen sonst noch über die Leitung gehen.

Prüfen und fertig

Egal für welchen Weg du dich entschieden hast, noch ein letzter Check:

  1. Terminal öffnen und curl -I https://deinedomain.de laufen lassen.
  2. Die Ausgabe durchsehen. Eine X-Powered-By-Zeile darf nirgends mehr auftauchen.
  3. Das Ganze für eine Admin-URL wiederholen (bei WordPress z.B. wp-login.php) und für eine Fehlerseite (eine zufällige Zeichenkette an die URL hängen, um einen 404 zu provozieren). Der Header darf überall weg bleiben.
  4. Wenn du Cloudflare oder einen Cache im Einsatz hast, zusätzlich von einer frischen IP testen oder den Cache umgehen, damit du eine Live-Antwort siehst und keine gecachte.
  5. Einen neuen InspectWP-Scan starten. Der PHP-Version-Check im Hosting-Bereich fällt nun auf andere Erkennungsmethoden zurück oder markiert die Version als nicht offengelegt. Genau das ist das Ziel.

Die PHP-Version zu verstecken ersetzt natürlich nicht, PHP aktuell zu halten. Es ist ein kleiner Baustein in einem größeren Härtungs-Bild. Aber es ist einer dieser Quick Wins, die fünf Minuten dauern, einem Angreifer eine frei verfügbare Information wegnehmen, und dich dafür exakt null kosten.

Prüfe jetzt deine WordPress-Seite

InspectWP analysiert deine WordPress-Seite auf Sicherheitslücken, SEO-Probleme, DSGVO-Konformität und Performance — kostenlos.

Seite kostenlos analysieren