Cabeceras de seguridad HTTP: qué son y cómo verificarlas
En resumen
Las cabeceras de seguridad HTTP que importan son seis: Strict-Transport-Security (HSTS), Content-Security-Policy (CSP), X-Frame-Options, X-Content-Type-Options (nosniff), Referrer-Policy y Permissions-Policy. Se añaden en Apache, Nginx, WordPress o PrestaShop y se comprueban con curl -I o con el análisis externo gratuito de WebIntacta. CSP conviene empezarla en modo observación (Report-Only) para no romper la web.
Las cabeceras de seguridad son unas pocas líneas que tu servidor envía con cada página y que le dicen al navegador qué puede hacer y qué no: usar solo HTTPS, no dejar que otra web te meta en un marco, no adivinar tipos de archivo. No sustituyen a tener la web actualizada, pero cierran ataques enteros a cambio de muy poco trabajo.
Por Jose SorianoPublicada: Última actualización: 7 min de lectura
Las seis que importan
Strict-Transport-Security (HSTS)
Le dice al navegador que, durante el tiempo indicado, solo entre a tu web por HTTPS, aunque alguien escriba http:// o pulse un enlace antiguo. Evita que un atacante en la misma red (una wifi pública) intercepte la primera visita sin cifrar.
Strict-Transport-Security: max-age=31536000; includeSubDomainsmax-age va en segundos (31536000 es un año). includeSubDomains lo extiende a todos los subdominios: actívalo solo si todos funcionan con HTTPS, incluidos los internos. La directiva preload sirve para entrar en la lista que los navegadores traen de fábrica; para eso hacen falta al menos un año de max-age, includeSubDomains, redirección de HTTP a HTTPS y certificado válido en todos los subdominios. Salir de esa lista tarda meses, así que no la añadas por probar.
Content-Security-Policy (CSP)
Es la que más protege y la más delicada. Define de qué orígenes puede cargar la página scripts, estilos, imágenes, fuentes o marcos. Si un atacante consigue inyectar un <script> que apunta a su dominio, una buena CSP impide que se ejecute. Es la defensa de fondo contra el XSS y contra muchos skimmers de tarjetas.
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; img-src 'self' data: https:; frame-ancestors 'self'; object-src 'none'; base-uri 'self'El problema: un WordPress o un PrestaShop con diez plugins carga recursos de muchos sitios, y una CSP mal hecha rompe el pago, el chat o las analíticas. Más abajo explico cómo implantarla sin romper nada.
X-Frame-Options y frame-ancestors
Impiden que otra web cargue la tuya dentro de un marco invisible para engañar al usuario y que haga clic donde no quiere (clickjacking). La forma moderna es la directiva frame-ancestors de la CSP; X-Frame-Options es la antigua, pero sigue siendo útil para navegadores viejos y no estorba tenerlas las dos. OWASP recomienda frame-ancestors si puedes, y si no, X-Frame-Options: DENY.
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'DENY prohíbe cualquier marco; SAMEORIGIN permite solo los de tu propio dominio (algunos editores visuales y constructores de páginas lo necesitan). Ojo: frame-ancestors no funciona si pones la CSP en una etiqueta <meta>; tiene que ir en la cabecera.
X-Content-Type-Options
Con nosniff, el navegador respeta el tipo de archivo que declara el servidor y no intenta adivinarlo. Evita que un archivo subido como imagen se ejecute como script. No tiene contraindicaciones: ponla siempre.
X-Content-Type-Options: nosniffReferrer-Policy
Controla cuánta información de la página de origen se envía al pulsar un enlace hacia otra web. Con strict-origin-when-cross-origin, a otros dominios solo les llega tu dominio, no la URL completa (que puede llevar parámetros con datos de un pedido o de una búsqueda). Es el valor que recomienda OWASP y el que usan por defecto los navegadores actuales, pero conviene declararlo.
Referrer-Policy: strict-origin-when-cross-originPermissions-Policy
Desactiva funciones del navegador que tu web no usa: cámara, micrófono, geolocalización… Si un script inyectado intenta pedirlas, el navegador se niega. Unos paréntesis vacíos significan «nadie».
Permissions-Policy: camera=(), microphone=(), geolocation=()Si tu tienda usa un método de pago que funciona con la API de pagos del navegador, o tu web tiene un mapa que pide la ubicación, no desactives esas funciones a ciegas: pruébalo.
Las que sobran
X-XSS-Protection está obsoleta: OWASP recomienda no enviarla o ponerla a 0, porque en navegadores antiguos podía crear fallos nuevos. Y conviene quitar X-Powered-By y las versiones de la cabecera Server, que solo le dicen al atacante qué software usas.
Cómo añadirlas
Apache (.htaccess)
En la mayoría de hostings compartidos con Apache, se añaden al .htaccess de la raíz. Necesita el módulo mod_headers, que casi todos tienen activo:
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always unset X-Powered-By
</IfModule>Nginx
En el bloque server del sitio. El always hace que se envíen también en las páginas de error:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
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;WordPress
Lo mejor es ponerlas en el servidor (.htaccess o Nginx), porque así cubren también imágenes y archivos estáticos. Si no tienes acceso, puedes enviarlas desde PHP con el gancho send_headers, en un plugin propio o en un mu-plugin (no en el functions.php del tema, que se pierde si cambias de tema):
<?php
add_action( 'send_headers', function () {
header( 'X-Content-Type-Options: nosniff' );
header( 'Referrer-Policy: strict-origin-when-cross-origin' );
header( 'Permissions-Policy: camera=(), microphone=(), geolocation=()' );
header( 'X-Frame-Options: SAMEORIGIN' );
} );Así solo se aplican a las páginas que genera WordPress, no a los archivos que sirve el servidor directamente. WordPress ya envía X-Frame-Options: SAMEORIGIN por su cuenta en la pantalla de acceso y en el escritorio.
PrestaShop
PrestaShop regenera el .htaccess cuando guardas ciertos ajustes (URL amigables, por ejemplo). Conserva lo que esté fuera del bloque que delimitan sus comentarios # ~~start~~ y # ~~end~~, así que pon tus cabeceras antes de # ~~start~~ o después de # ~~end~~, nunca dentro. Si la tienda va en Nginx, en la configuración del servidor como arriba.
CSP sin romper la web
La forma segura es empezar en modo observación con Content-Security-Policy-Report-Only. El navegador evalúa la política y avisa de lo que habría bloqueado, pero no bloquea nada:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'- Publica la política en modo Report-Only.
- Navega por la web con las herramientas de desarrollo abiertas (pestaña Consola): cada recurso que la política habría bloqueado aparece como aviso. Recorre sobre todo el carrito, el pago y los formularios.
- Añade a la política los orígenes legítimos (pasarela de pago, analíticas, chat). Si quieres recibir los avisos de los visitantes reales, configura un destino con
report-to(yreport-uripara navegadores que aún no lo soportan). - Cuando pasen días sin avisos nuevos, cambia el nombre de la cabecera a
Content-Security-Policy.
Report-Only no protege por sí sola: solo informa. No te quedes ahí indefinidamente. Y si la web depende de muchos scripts en línea, una CSP con 'unsafe-inline' en script-src protege mucho menos; aun así, frame-ancestors, object-src 'none' y base-uri 'self' aportan desde el primer día.
Cómo verificarlas
En el navegador
Abre tu web, pulsa F12, ve a la pestaña Red (Network), recarga y haz clic en la primera petición (el documento). En Encabezados de respuesta verás todas las cabeceras que envía el servidor.
Con curl
Desde la terminal de Windows, macOS o Linux:
curl -sI https://tu-web.com/Por ejemplo, esta web responde (entre otras) con:
strict-transport-security: max-age=63072000; includeSubDomains; preload
content-security-policy: default-src 'self'; ... frame-ancestors 'none'; object-src 'none'; upgrade-insecure-requests
x-content-type-options: nosniff
x-frame-options: DENY
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()Comprueba también una página interior, una imagen y una URL que no exista: es donde suelen faltar.
Con el análisis gratuito
El análisis gratuito revisa las cabeceras de tu portada, la calidad de la CSP (si solo está en Report-Only, si permite scripts de cualquier sitio) y el HTTPS. Si necesitas que alguien las configure y compruebe que no rompen el pago, está incluido en el mantenimiento de WordPress y en la revisión de seguridad de WordPress.
El mismo análisis lee también tu robots.txt y comprueba si publicas un llms.txt, el archivo que resume tu web para los asistentes de IA. Qué es, en qué se diferencia de robots.txt y cómo escribirlo, en la guía de llms.txt.
Fuentes
Preguntas frecuentes
¿Qué son las cabeceras de seguridad HTTP?
Son líneas que el servidor envía con cada respuesta para indicar al navegador cómo comportarse: usar solo HTTPS, qué scripts puede cargar, si la página puede mostrarse dentro de un marco o qué funciones del navegador están permitidas.
¿Cómo veo las cabeceras de seguridad de una web?
En el navegador, con F12 → Red → la primera petición → Encabezados de respuesta. Desde la terminal, con curl -sI https://tu-web.com/. El análisis gratuito de WebIntacta también las revisa.
¿Puede una CSP romper mi web?
Sí, si bloquea un script que necesitas, como el de la pasarela de pago. Por eso se empieza con Content-Security-Policy-Report-Only, que solo avisa, y se pasa a la versión que bloquea cuando ya no aparecen avisos.
¿X-Frame-Options sigue siendo necesaria?
La directiva frame-ancestors de la CSP la sustituye en los navegadores actuales, pero X-Frame-Options sigue sirviendo para los antiguos y no estorba tener las dos.
¿Cómo añado cabeceras de seguridad en WordPress?
Lo mejor es hacerlo en el servidor, en el .htaccess (Apache) o en la configuración de Nginx. Si no tienes acceso, un pequeño plugin con el gancho send_headers puede enviarlas desde PHP.