Guía de solución

Cómo ocultar tu versión de PHP en las cabeceras de respuesta HTTP

19 de abril de 2026

Abre la pestaña de red del navegador en cualquier sitio basado en PHP y mira las cabeceras de respuesta del documento HTML principal. En muchas instalaciones encontrarás una cabecera del estilo X-Powered-By: PHP/8.1.27. Eso es PHP avisando amablemente a todo internet de qué versión está corriendo. Es un detalle minúsculo que la mayoría de propietarios de sitios nunca nota, pero resulta ser una de las piezas de información más fáciles que un atacante recopila antes de decidir si vale la pena seguir investigando un sitio.

Esta guía repasa por qué existe esa cabecera, qué expone realmente y todas las formas realistas de eliminarla. Desde la configuración del servidor hasta el nivel de un único sitio WordPress en hosting compartido donde no puedes tocar el php.ini.

Lo que la cabecera revela realmente a un atacante

Un valor como PHP/8.1.27 revela dos datos útiles. Primero, la versión mayor. Si todavía estás en PHP 7.4 o PHP 8.0, ambas ya en final de vida útil, un atacante sabe inmediatamente que no van a llegar más parches de seguridad. Segundo, el nivel de parche exacto. Entre dos releases de parche suele haber un puñado de CVE conocidos públicamente. Un escáner puede comparar tu versión reportada con su base de datos de CVE y decidir en milisegundos si seguir hurgando o pasar de largo.

Ocultar la versión no parchea nada, evidentemente. Si tu PHP está desactualizado, sigue desactualizado. Pero quitar el aviso hace tres cosas concretas: evita que escáneres automatizados marquen tu sitio como objetivo fácil, encarece ligeramente el reconocimiento manual y elimina una pieza de información que sencillamente no tiene por qué estar en tus cabeceras de respuesta. No hay ningún motivo legítimo para que internet sepa tu nivel de parche de PHP.

Algunos marcos de cumplimiento y listas de seguridad (PCI-DSS, BSI IT-Grundschutz, auditorías internas en empresas grandes) piden explícitamente que se suprima esta cabecera. Aunque no trabajes en ninguno de esos contextos, es un arreglo de cinco minutos sin ningún inconveniente.

¿De dónde viene la cabecera?

La cabecera X-Powered-By la emite el propio PHP, no tu servidor web. El comportamiento se controla con una sola directiva del php.ini llamada expose_php. Cuando esa directiva está en On (el valor por defecto en la mayoría de distribuciones de PHP), PHP añade la cabecera a cada respuesta automáticamente. Desactívala y la cabecera desaparece para todos los sitios de esa instalación de PHP.

Encima de eso, algunos frameworks o aplicaciones añaden sus propios valores X-Powered-By. Express, ASP.NET y otros lo hacen. WordPress en sí no lo hace, pero algunos plugins sí. Por eso, después de desactivar la divulgación de versión de PHP, conviene revisar las cabeceras de respuesta una vez más para ver si algo más sigue filtrando información.

Opción 1: Desactivar expose_php en php.ini

Es la solución más limpia si controlas el servidor. Abre tu archivo php.ini. En Linux la ruta suele ser una de estas:

  • /etc/php/8.3/fpm/php.ini para PHP-FPM, el setup más habitual detrás de nginx y de Apache moderno
  • /etc/php/8.3/apache2/php.ini para Apache con mod_php
  • /etc/php/8.3/cli/php.ini para la línea de comandos, que normalmente no te importa en este contexto

El número de versión en la ruta coincidirá con el PHP que tengas. Busca la línea que pone:

expose_php = On

Cámbiala a:

expose_php = Off

Guarda el archivo y reinicia el proceso PHP para que el cambio surta efecto. Para PHP-FPM eso es:

sudo systemctl restart php8.3-fpm

Para Apache con mod_php:

sudo systemctl restart apache2

Para verificar, lanza un curl rápido contra tu sitio y mira las cabeceras:

curl -I https://tudominio.com

La línea X-Powered-By debería haber desaparecido por completo. Si sigue ahí, has editado el php.ini equivocado (a menudo hay más de uno). Comprueba cuál se carga realmente con php --ini en la línea de comandos, o poniendo una llamada temporal a phpinfo() en el sitio (y eliminándola inmediatamente después, por favor).

Opción 2: Establecer expose_php por sitio mediante .user.ini

No todo el mundo tiene acceso al php.ini global. En muchos hosts compartidos aún puedes influir en los ajustes de PHP a través de un archivo llamado .user.ini, que colocas en el document root de tu sitio. Crea (o edita) un .user.ini con:

expose_php = Off

PHP comprueba este archivo en cada petición, pero cachea el resultado durante unos minutos por defecto (controlado por user_ini.cache_ttl, normalmente 300 segundos). Así que dale cinco minutos, vacía cualquier caché de borde y vuelve a comprobar las cabeceras.

Una advertencia: no todos los hosts permiten que expose_php se sobrescriba desde .user.ini. La directiva tiene el modo PHP_INI_PERDIR, que técnicamente lo permite, pero algunos hosts hacen una lista blanca explícita de lo que se puede cambiar. Si el ajuste no surte efecto tras unos minutos, probablemente tu host lo está bloqueando. Salta a la Opción 5.

Opción 3: Eliminar la cabecera en Apache

También puedes eliminar la cabecera a nivel de servidor web, lo que funciona haga lo que haga PHP internamente. En tu configuración de Apache (o en el .htaccess del sitio, si AllowOverride lo permite), añade:

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

El motivo de usar tanto unset como always unset es que la versión normal solo se aplica a respuestas 2xx correctas. La variante always también se aplica a respuestas de error como las páginas 500, que es justo donde tampoco querrías que la versión se filtrara.

Para que esto funcione, mod_headers tiene que estar habilitado. En Debian y Ubuntu:

sudo a2enmod headers
sudo systemctl reload apache2

Si estás en hosting compartido con Apache, la variante con .htaccess casi siempre funciona, porque la mayoría de hosters tiene mod_headers habilitado y lo permiten vía AllowOverride All. Si la regla no surte efecto, pregunta a tu host si mod_headers está disponible.

Opción 4: Eliminar la cabecera en nginx

En nginx el equivalente va dentro del bloque server de tu sitio, o en un bloque http general si lo quieres a nivel de todo el servidor:

more_clear_headers 'X-Powered-By';

Pequeña pega: more_clear_headers requiere el módulo headers-more-nginx-module, que no forma parte del paquete estándar de nginx en todas las distribuciones. Debian y Ubuntu lo incluyen en el paquete nginx-extras:

sudo apt install nginx-extras

Si estás en un nginx pelado sin el módulo extras, hay un workaround usando fastcgi_hide_header. De hecho, esta es la herramienta correcta si la cabecera viene de PHP-FPM (que en la mayoría de casos así es):

location ~ \.php$ {
    # tu fastcgi_pass habitual y los params
    fastcgi_hide_header X-Powered-By;
}

Recarga nginx con sudo nginx -t && sudo systemctl reload nginx y verifica con curl -I.

Opción 5: Caso especial, WordPress

WordPress no establece por sí mismo X-Powered-By. La cabecera viene de PHP. Por tanto, todo lo anterior aplica sin cambios: desactivar expose_php o eliminar la cabecera en Apache o nginx también lo soluciona para un sitio WordPress. Pero WordPress te da dos enfoques adicionales que conviene conocer.

Suprimir la cabecera desde dentro de WordPress. Si no tienes acceso a la configuración del servidor web y .user.ini no funciona en tu host, puedes eliminar la cabecera de forma programática mediante un must-use plugin. Crea un archivo en wp-content/mu-plugins/remove-powered-by.php (crea la carpeta mu-plugins si todavía no existe) y mete esto dentro:

<?php
/**
 * Plugin Name: Remove X-Powered-By
 * Description: Strips the X-Powered-By header from every WordPress response.
 */

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

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

Dos cosas a tener en cuenta con este enfoque. Primero, header_remove() solo funciona si PHP aún no ha empezado a enviar el cuerpo de la respuesta. El hook send_headers se dispara en el momento adecuado para cualquier página normal de WordPress, así que esto funciona para entradas, páginas, el admin y la API REST. No ayuda con peticiones que nunca llegan a WordPress, como llamadas directas a archivos estáticos o páginas cacheadas servidas por un proxy inverso. Segundo, algunos hosts ejecutan un proxy inverso (Cloudflare, Varnish, nginx delante de Apache) que lee la cabecera de la respuesta del backend y la reenvía tal cual. Si la cabecera sigue apareciendo en tu navegador después de instalar este plugin, el proxy la está cacheando. Vacía la caché y, idealmente, elimina también la cabecera a nivel de proxy.

WordPress también expone su propia versión. Ya que estás, probablemente quieras lidiar con la etiqueta <meta name="generator" content="WordPress X.Y.Z"> que WordPress mete en el head del HTML, y con los parámetros ?ver=X.Y.Z en los archivos CSS y JS. No son lo mismo que la cabecera de versión de PHP, pero filtran la versión exacta de WordPress, lo que es comparablemente útil para un atacante. El fragmento de abajo se ocupa de ambos:

<?php
// Eliminar la meta-etiqueta generator
remove_action('wp_head', 'wp_generator');

// Quitar la versión de WordPress de feeds RSS, admin, etc.
add_filter('the_generator', '__return_empty_string');

// Eliminar ?ver=X.Y.Z de las URLs de archivos CSS y JS
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);

Ten en cuenta que eliminar el parámetro ?ver= tiene un efecto colateral: el navegador ya no puede saber, solo por la URL, cuándo ha cambiado un archivo CSS o JS. Si dependes de ello para invalidar caché, o bien deja el filtro fuera o reemplaza ver por un hash del contenido del archivo.

Opción 6: Cloudflare, Sucuri y otros proxies inversos

Si estás detrás de Cloudflare o un CDN similar, puedes eliminar la cabecera una capa más afuera, lo cual está bien porque cubre todos los sitios que enrutas por la misma zona sin tocar el origen. En Cloudflare se hace mediante una Transform Rule del tipo "Modify Response Header":

  • Nombre de la regla: "Remove X-Powered-By"
  • Cuando las peticiones entrantes coincidan con: Hostname equals tudominio.com (o un comodín si quieres aplicarlo a toda la zona)
  • Entonces: Remove header X-Powered-By

Sucuri, BunnyCDN, Fastly y la mayoría de los proxies inversos ofrecen un equivalente. La denominación exacta en el panel varía, pero la función que buscas siempre es "modificar cabeceras de respuesta" o "edge rules".

Otras cabeceras que conviene comprobar de paso

PHP no es lo único que tiende a hablar mucho de sí mismo en las cabeceras de respuesta. Ya que tienes curl abierto, repasa las cabeceras buscando alguna de las siguientes:

  • Server: Apache/2.4.58 (Ubuntu) revela la versión de parche de Apache y tu sabor de SO. Suprímelo con ServerTokens Prod y ServerSignature Off en Apache, o server_tokens off en nginx.
  • X-Powered-CMS, X-Generator, X-Drupal-Cache o similares. Los suelen poner plugins o módulos específicos de un CMS. Elimínalas igual que X-Powered-By.
  • X-AspNet-Version o X-AspNetMvc-Version. Solo relevantes si algo de tu stack toca .NET, pero conviene saberlo.

InspectWP marca todas estas en la sección de cabeceras de seguridad de tu informe. Una vez ocultada la versión de PHP, lanzar un escaneo nuevo es la forma más rápida de ver qué otros pequeños descuidos siguen viajando por la red.

Verificar y listo

Sea cual sea la vía que hayas elegido, haz una última comprobación:

  1. Abre un terminal y ejecuta curl -I https://tudominio.com.
  2. Revisa la salida. No debería haber ninguna línea X-Powered-By.
  3. Repite para una URL del admin (en WordPress, prueba wp-login.php) y para una página de error (añade una cadena aleatoria a la URL para provocar un 404). La cabecera debe seguir desaparecida en todas partes.
  4. Si usas Cloudflare o una caché, prueba también desde una IP nueva o saltándote la caché, para asegurarte de que estás viendo una respuesta en vivo y no una cacheada.
  5. Lanza un nuevo escaneo de InspectWP. La comprobación de la versión de PHP en la sección de Hosting ahora caerá a otros métodos de detección o marcará la versión como no divulgada, que es lo que quieres.

Ocultar la versión de PHP no sustituye a mantener PHP actualizado. Es un pequeño paso dentro de un panorama de fortificación más amplio. Pero es de esos triunfos rápidos que cuestan cinco minutos, eliminan una pieza de información gratuita que los atacantes obtendrían sin esfuerzo y no te cuestan absolutamente nada a cambio.

Analiza tu sitio de WordPress ahora

InspectWP analiza tu sitio de WordPress en busca de problemas de seguridad, SEO, cumplimiento del RGPD y rendimiento, gratis.

Analiza tu sitio gratis