Cabeceras de seguridad en WordPress: qué añadir y qué puede romper tu web
Un escaneo de seguridad dice que a tu web le faltan cabeceras: HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. Son líneas que tu servidor envía con cada página y que le dicen al navegador con cuánto rigor tratarla. WordPress casi no envía ninguna en las páginas que ven los visitantes. Tres se pueden añadir hoy sin riesgo real. Las otras tres necesitan un pequeño plan, porque un valor equivocado puede dejar fuera a los visitantes o bloquear tus propios scripts. Esta guía repasa cada una, dónde configurarla en WordPress y por qué las cabeceras que estaban el mes pasado vuelven a faltar.
Qué son las cabeceras de seguridad y por qué importa que falte una
Cada respuesta de tu servidor empieza con cabeceras: el tipo de contenido, las reglas de caché, las cookies. Las cabeceras de seguridad son unas cuantas más que activan protecciones que el navegador ya tiene pero que no usa si la web no las pide. X-Frame-Options impide que otra web cargue la tuya en un marco oculto para engañar a los visitantes y hacerles hacer clic (clickjacking). HSTS le dice al navegador que use solo HTTPS, aunque alguien escriba la dirección sin él. Content-Security-Policy indica desde dónde pueden cargarse scripts, lo que limita lo que puede hacer el código inyectado.
Que falte una cabecera no es un agujero en sí, ni significa que la web haya sido hackeada. Significa que una de las defensas del navegador está apagada. Por eso los escáneres las marcan como avisos y no como fallos, y por eso vale la pena añadir las seguras y planificar el resto, en lugar de ignorar la lista entera.
El núcleo de WordPress envía X-Frame-Options: SAMEORIGIN (y una regla frame-ancestors equivalente) en la página de acceso y en wp-admin. La parte pública de la web —las páginas que revisa un escaneo— no recibe ninguna de las seis salvo que las añada tu hosting, tu CDN o un plugin.
Cómo ver cuáles envía tu web
En el navegador: abre la página de inicio, luego Herramientas de desarrollo → Red, recarga, haz clic en la primera petición (la página en sí) y lee las cabeceras de respuesta. Desde una terminal, el comando de abajo sigue las redirecciones como un navegador e imprime solo las seis cabeceras de seguridad. La que no aparezca en la salida falta.
Revisa la página final después de las redirecciones, por HTTPS. Una cabecera que envía la dirección http:// pero no la página https:// sigue faltando para los visitantes.
curl -sIL https://example.com/ | grep -iE "strict-transport-security|content-security-policy|x-frame-options|x-content-type-options|referrer-policy|permissions-policy"
Las tres que puedes añadir hoy
X-Content-Type-Options: nosniff impide que el navegador adivine el tipo de un archivo: un archivo servido como texto no se ejecuta como script, y un script o una hoja de estilos con el tipo de contenido equivocado se bloquea. En una web WordPress normal no cambia nada, porque WordPress, el servidor web y los plugins bien hechos ya envían los tipos correctos.
X-Frame-Options: SAMEORIGIN deja que tus propias páginas se incrusten entre sí, pero impide que cualquier otra web incruste la tuya. Lo único que rompe es una página que incrustas a propósito en otro dominio, como un formulario de reservas dentro de la web de un socio. Si lo haces, usa en su lugar la directiva frame-ancestors de Content-Security-Policy, que acepta una lista de webs permitidas.
Referrer-Policy: strict-origin-when-cross-origin envía a otras webs solo tu dominio, no la dirección completa de la página de la que venía el visitante, y nada en absoluto de HTTPS a HTTP. Es también lo que hacen los navegadores actuales cuando no hay ninguna política, así que añadirla sobre todo hace explícito ese comportamiento. De las seis, es la de menor riesgo si falta y la más fácil de añadir.
En Apache (la mayoría de los hostings compartidos de WordPress), pon estas líneas en .htaccess encima del bloque # BEGIN WordPress: WordPress solo reescribe lo que hay entre sus propias marcas, así que las líneas se mantienen al guardar los enlaces permanentes. La línea Permissions-Policy se explica más abajo; déjala fuera hasta haber leído esa parte.
<IfModule mod_headers.c> Header set X-Content-Type-Options "nosniff" Header set X-Frame-Options "SAMEORIGIN" Header set Referrer-Policy "strict-origin-when-cross-origin" Header set Permissions-Policy "camera=(), microphone=(), geolocation=()" </IfModule>
HSTS: solo cuando HTTPS funcione en todas partes
Strict-Transport-Security hace que el navegador recuerde, durante max-age segundos, que la web es solo HTTPS. Evita la primera petición por HTTP sin cifrar —la que un atacante en una wifi pública puede interceptar— y convierte los avisos de certificado en errores que los visitantes no pueden saltarse. Los navegadores ignoran la cabecera si llega por HTTP, así que solo funciona desde una página HTTPS.
El riesgo es esa memoria. Si el certificado caduca o un subdominio sigue en HTTP, los navegadores que han visto la cabecera se niegan a cargarlo hasta que pase max-age, y solo puedes desactivarla (max-age=0) para los navegadores que vuelvan a llegar a la web por HTTPS. Por eso conviene activarla por pasos: empieza con max-age=300 (cinco minutos), revisa todas las páginas y subdominios, y luego súbela a un día, una semana y por fin 31536000 (un año). Añade includeSubDomains solo cuando todos los subdominios —correo, tienda, staging— sirvan HTTPS.
La directiva preload es otra decisión. Pide a los navegadores que incluyan tu dominio en su lista interna de webs solo HTTPS; exige un año de max-age más includeSubDomains, y salir de la lista lleva mucho tiempo. Déjala fuera salvo que estés seguro. Si la web está detrás de Cloudflare, HSTS es un ajuste en SSL/TLS → Edge Certificates, no una línea del servidor.
Content-Security-Policy: empieza en modo report-only
Una Content-Security-Policy completa enumera todos los orígenes desde los que pueden cargarse scripts, estilos, fuentes, imágenes y marcos. Es la más fuerte de las seis contra el código inyectado, y la que más probablemente rompa una web WordPress: temas, maquetadores, analítica, banners de consentimiento, chats y botones de pago cargan scripts desde algún sitio, a menudo en línea. Copiar una política estricta de un tutorial bloqueará algunos.
Envíala primero como Content-Security-Policy-Report-Only. El navegador no bloquea nada y registra cada infracción en la consola, así que puedes recorrer la web —también el checkout— y añadir cada origen que de verdad necesitas. Cambia el nombre de la cabecera a Content-Security-Policy solo cuando la consola quede en silencio.
Una política corta que rara vez rompe algo es un primer paso razonable mientras trabajas en la completa: object-src 'none'; base-uri 'self'; frame-ancestors 'self'. Bloquea incrustaciones de plugins antiguos, impide que el código inyectado cambie adónde apuntan los enlaces relativos y hace lo mismo que X-Frame-Options: SAMEORIGIN.
Configúrala como cabecera. Una política en una etiqueta <meta http-equiv> funciona para la mayoría de las directivas, pero frame-ancestors se ignora ahí, y las herramientas que leen las cabeceras de respuesta no la ven.
Permissions-Policy: apaga lo que no usas
Permissions-Policy decide qué funciones del navegador —cámara, micrófono, ubicación y otras— pueden pedir la página y todo lo que incrusta. camera=(), microphone=(), geolocation=() apaga esas tres en toda la web, de modo que un script inyectado en una página o un anuncio incrustado no puede pedírselas a los visitantes.
Incluye solo funciones que sabes que la web no usa. Un buscador de tiendas que pide la ubicación del visitante necesita geolocation=(self) en lugar de geolocation=(); un plugin de videollamadas o grabación necesita cámara y micrófono. Revisa las páginas que usan esas funciones después de añadirla.
Dónde configurarlas en WordPress
Hay tres sitios, y cuanto más arriba, más cubren. El servidor web (.htaccess en Apache, el bloque server en Nginx) cubre todas las respuestas, incluidas imágenes, scripts y páginas en caché. Una CDN delante de la web cubre todo lo que pasa por ella. Dentro de WordPress —un plugin de seguridad, unas líneas en el hook send_headers o "Add safe security headers" de Relvato— solo se cubren las páginas que genera el propio WordPress; una página que sirve una caché antes de que WordPress cargue del todo puede salir sin ellas. Está bien como arreglo rápido, pero comprueba el resultado en la página de inicio real.
En Nginx, usa add_header con always para que la cabecera se envíe también en las páginas de error. Cuidado con una trampa: un bloque location que tiene algún add_header propio no hereda los del bloque server. Alguien añade una cabecera de caché a una location y las cabeceras de seguridad desaparecen de ella sin avisar.
Configura cada cabecera en un solo sitio. Dos capas que añaden la misma cabecera la envían dos veces, y dos cabeceras Content-Security-Policy se aplican las dos, así que la política efectiva es más estricta que cualquiera de ellas.
add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Por qué las cabeceras vuelven a desaparecer
Las cabeceras casi nunca se quitan a propósito. Desaparecen cuando cambia el sitio que las ponía: una mudanza a otro hosting (Nginx no lee .htaccess en absoluto, así que todas sus reglas dejan de funcionar), una CDN nueva o un ajuste de la CDN restablecido, un plugin de caché sustituido por otro que sirve páginas antes de que cargue WordPress, un plugin de seguridad desactivado o una opción suya restablecida por una actualización, o un nuevo bloque location de Nginx que deja de heredarlas.
Nada de esto produce un error. La web se ve igual, así que la única forma de notarlo es volver a revisar las cabeceras después de cada cambio, justo lo que nadie hace a mano tras cada actualización.
Cómo lo detecta Relvato
El monitor Exposure scan de Relvato pide tu página de inicio como lo haría un navegador, siguiendo las redirecciones, y lee las cabeceras de respuesta. Funciona en cualquier web, no solo en WordPress. Cada cabecera que falta es un aviso propio —"Missing security header: HSTS", "Missing security header: Content-Security-Policy", etc.— en una ejecución que por lo demás pasa. Como cada uno va por separado, puedes pulsar "Ignore this warning" en uno que has decidido dejar por ahora, como CSP, y seguir enterándote de los demás. Solo lee cabeceras, así que una política en una etiqueta meta no cuenta.
En WordPress con el plugin de Relvato, las tres cabeceras seguras muestran "Fix in WordPress" junto al aviso. En WordPress → Relvato, un clic activa "Add safe security headers": X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin y X-Frame-Options: SAMEORIGIN, cada una solo si la web no la envía ya. "Switch off" la vuelve a apagar allí. HSTS, Content-Security-Policy y Permissions-Policy nunca se añaden por ti, porque un valor equivocado rompe webs: consulta lo que Relvato no arregla por ti.
En el plan Free pulsas Run cuando quieras el escaneo, o lo programas cada semana. Pro vuelve a ejecutar tus monitores por sí solo después de cada actualización, así que una cabecera que se llevó una actualización de plugin o un cambio de hosting vuelve a aparecer como aviso sin que nadie tenga que acordarse de mirar.
Las seis cabeceras de seguridad de un vistazo
| Cabecera | Qué hace | Un valor seguro para empezar | ¿Puede romper algo? |
|---|---|---|---|
| X-Content-Type-Options | Impide que el navegador adivine tipos de archivo | nosniff | Casi nunca en WordPress |
| X-Frame-Options | Impide que otras webs incrusten la tuya (clickjacking) | SAMEORIGIN | Solo páginas que incrustas en otros dominios |
| Referrer-Policy | Limita lo que otras webs saben de la página de la que viene el visitante | strict-origin-when-cross-origin | No: es el valor por defecto del navegador |
| Permissions-Policy | Apaga cámara, micrófono, ubicación y otras funciones | camera=(), microphone=(), geolocation=() | Sí, si una página usa alguna de esas funciones |
| Strict-Transport-Security (HSTS) | Hace que los navegadores usen solo HTTPS | max-age=300, subido por pasos | Sí: deja fuera subdominios HTTP y certificados caducados |
| Content-Security-Policy | Indica desde dónde pueden cargarse scripts, estilos y marcos | Primero Report-Only; object-src 'none'; base-uri 'self'; frame-ancestors 'self' | Sí: la que más probablemente bloquee tus propios scripts |
Preguntas frecuentes
¿Qué cabecera de seguridad debo añadir primero?
X-Content-Type-Options, X-Frame-Options y Referrer-Policy, juntas: son seguras en casi cualquier web WordPress y ocupan unas pocas líneas. Luego Permissions-Policy para las funciones que no usas, después HSTS por pasos y Content-Security-Policy al final, primero en modo report-only.
¿Las cabeceras de seguridad ayudan al SEO?
Google no incluye las cabeceras de seguridad entre sus señales de posicionamiento; HTTPS sí lo es. HSTS ayuda porque mantiene a los visitantes en HTTPS. La razón de verdad para añadirlas es proteger a los visitantes, no el posicionamiento.
¿Puede un plugin añadirlas por mí?
Sí, y varios plugins de seguridad lo hacen. Un plugin solo las añade a las páginas que genera WordPress, así que revisa después la página de inicio real: una caché de páginas o una CDN pueden servir páginas sin ellas. Configura cada cabecera en un solo sitio para que no se envíe dos veces.
He añadido una etiqueta meta de CSP. ¿Por qué se sigue diciendo que falta la cabecera?
El escaneo lee las cabeceras de respuesta, y una política en <meta http-equiv> no es una de ellas. Los navegadores aplican la mayoría de las directivas de la etiqueta meta, pero no frame-ancestors. Enviada como cabecera, la política lo cubre todo.
¿Que falte una cabecera de seguridad significa que mi web ha sido hackeada?
No. Significa que una protección del navegador está apagada. Si te preocupa una intrusión, las señales son otras: consulta la checklist para un WordPress hackeado.
Fuentes
- OWASP — HTTP Security Response Headers Cheat Sheet
- MDN — Strict-Transport-Security
- MDN — Content-Security-Policy
- MDN — X-Frame-Options
- MDN — X-Content-Type-Options
- MDN — Referrer-Policy
- MDN — Permissions-Policy
- WordPress Developer Resources — send_frame_options_header()
- nginx — ngx_http_headers_module (add_header)
- HSTS Preload List Submission