Cada respuesta de tu servidor puede darle instrucciones al navegador: usar solo HTTPS, no ejecutar scripts que no autorizaste, no dejar que otro sitio te muestre en un iframe. Son las cabeceras de seguridad HTTP. Configurarlas cuesta poco, pero una mal puesta puede dejar el sitio en blanco o, en el caso de HSTS, dejar a tus visitantes sin acceso hasta que venza el plazo que fijaste.
Tampoco conviene perseguir una nota. El HTTP Observatory de Mozilla advierte en su propia documentación que sus rangos de notas son "essentially arbitrary", y securityheaders.com explica que su puntaje refleja cuántas cabeceras de seguridad envía tu sitio. Una nota alta no garantiza que estén bien configuradas para tu sitio.
Esta guía revisa qué configurar, qué ya no sirve, cómo desplegar sin romper nada y cómo comprobarlo, según MDN, OWASP, el W3C, el IETF y Chrome, consultados el 4 de noviembre de 2026.
Content-Security-Policy: la que más protege y la que más rompe
CSP define qué código puede ejecutar la página. Sirve sobre todo contra el cross-site scripting (XSS) y también contra el clickjacking, pero OWASP aclara que no debe ser tu única defensa contra XSS.
Política estricta con nonce
Mantener una política con listas de dominios permitidos es trabajoso y, según web.dev, se puede eludir en la mayoría de las configuraciones. web.dev, MDN, OWASP y el W3C recomiendan en cambio la "CSP estricta":
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';- El nonce es un valor aleatorio que el servidor genera en cada respuesta y repite en cada
<script nonce="...">legítimo. web.dev pide que sea criptográficamente fuerte, idealmente de 128 bits o más, y nuevo cada vez. Si se puede adivinar, no protege. 'strict-dynamic'deja que un script autorizado cargue otros. Facilita usar herramientas de terceros, aunque la especificación recomienda evitarlo cuando se pueda.https:y'unsafe-inline'son solo respaldos para navegadores antiguos. Los navegadores que entienden nonces y'strict-dynamic'los ignoran.object-src 'none'bloquea plugins ybase-uriimpide inyectar etiquetas<base>. La especificación acepta'none'o'self'; usa'self'si tu sitio tiene su propia etiqueta<base>.
El nonce exige generar HTML en el servidor. Para sitios estáticos o páginas en caché, web.dev recomienda usar hashes ('sha256-...') de cada script en línea. Y antes de activarla, mueve a archivos JavaScript los manejadores como onclick="..." y los enlaces javascript:: una CSP estricta los bloquea.
Prueba primero en modo Report-Only
Content-Security-Policy-Report-Only evalúa la política sin bloquear nada y reporta lo que habría bloqueado. Según MDN, sin la directiva report-to los reportes no se envían. El destino se declara en otra cabecera:
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'self'; report-to csp; report-uri /csp-reportsreport-uri está obsoleta, pero MDN recomienda declarar ambas mientras report-to no funcione en todos los navegadores. La especificación permite enviar a la vez una política aplicada y otra en prueba.
frame-ancestors contra el clickjacking
frame-ancestors 'none' impide que cualquier sitio muestre tu página en un iframe; 'self' lo permite solo a tu propio origen. MDN recomienda 'none' salvo que necesites que tu sitio sea embebible.
Ojo: en una etiqueta <meta> la especificación no acepta frame-ancestors, report-uri, sandbox ni el modo Report-Only. Van como cabecera HTTP.
Strict-Transport-Security y preload
HSTS le indica al navegador que, durante max-age segundos, use solo HTTPS con tu dominio y que no deje al usuario saltarse errores de certificado. Lo define el RFC 6797, de noviembre de 2012, con dos directivas: max-age e includeSubDomains.
- Solo cuenta por HTTPS. El RFC obliga al navegador a ignorar la cabecera si llega por HTTP.
- La primera visita queda expuesta. Mientras el navegador no haya recibido la cabecera, una primera petición por HTTP puede ser interceptada. La lista de precarga existe para cerrar esa brecha.
includeSubDomainsalcanza a todos los subdominios, también a los antiguos que aún no tengan HTTPS.
La directiva preload no es parte del RFC. La lista se gestiona en hstspreload.org, un servicio alojado por Google, y según MDN la usan todos los navegadores. Para entrar, el dominio debe servir HTTPS en todos sus subdominios, incluido www si existe, y enviar max-age de al menos 31536000 segundos (un año) con includeSubDomains y preload.
El riesgo está en salir de la lista. hstspreload.org advierte que la inclusión no se puede deshacer fácilmente: la eliminación puede tardar de 6 a 12 semanas en llegar a la mayoría de los usuarios de Chrome, y más en otros navegadores. También pide no incluir preload por defecto en plantillas: no lo copies de un ejemplo.
Además, hstspreload.org recomienda HSTS, pero no recomienda la precarga: su beneficio adicional es mínimo frente al de HSTS. Considérala solo si aceptas que salir de la lista toma meses.
El despliegue que propone hstspreload.org sube max-age por etapas: 300 segundos (5 minutos), 604800 (una semana) y 2592000 (un mes), esperando el plazo completo de cada etapa antes de pasar a la siguiente. Si algo falla, max-age=0 desactiva HSTS, pero solo en los navegadores que vuelvan a visitarte por HTTPS.
Tres cabeceras simples
X-Content-Type-Options: nosniffhace que el navegador respete elContent-Typedeclarado. Bloquea scripts y hojas de estilo que lleguen con otro tipo, así que revisa antes que tu servidor los declare bien.Referrer-Policy: strict-origin-when-cross-originenvía la URL completa dentro de tu sitio, solo el origen a otros sitios con HTTPS y nada al pasar a HTTP. Ya es el valor por defecto de los navegadores, pero OWASP recomienda declararlo. Si tus URL llevan datos sensibles,no-referrerno envía nada.Permissions-Policydesactiva funciones que no usas, como cámara, micrófono o geolocalización, también dentro de iframes de terceros. MDN la marca como experimental y con soporte parcial.
Aislamiento entre orígenes: COOP, CORP y COEP
Cross-Origin-Opener-Policy: same-originpone tu página en un grupo de navegación separado de las ventanas de otros orígenes, que pierden la referencia mediantewindow.opener. Si usas ventanas emergentes de inicio de sesión o de pago de terceros, MDN indicasame-origin-allow-popupspara conservar esa referencia.Cross-Origin-Resource-Policydice quién puede incrustar tus recursos. web.dev recomienda enviarla en todos, porque un atacante puede cargarlos desde otro sitio para inferir información. OWASP sugieresame-site; usacross-originen recursos que otros sitios deban cargar.Cross-Origin-Embedder-Policy: require-corpsolo hace falta si necesitas aislamiento entre orígenes, por ejemplo para usarSharedArrayBuffer. OWASP advierte que bloquea los recursos de otros orígenes mal configurados. Si no la necesitas, no la actives.
Cookies
Las cookies de sesión se protegen en la propia Set-Cookie:
Set-Cookie: __Host-sesion=VALOR; Path=/; Secure; HttpOnly; SameSite=LaxSecure: la cookie solo viaja por HTTPS.HttpOnly: JavaScript no puede leerla, lo que reduce el impacto de un XSS. Se sigue enviando en las peticiones del sitio.SameSite:Strictla envía solo en peticiones de tu propio sitio;Laxtambién en algunas navegaciones desde otros sitios;Noneen todas y exigeSecure. Algunos navegadores aplicanLaxsi no la declaras, así que declárala.- Prefijo
__Host-: el navegador solo acepta la cookie si tieneSecure, viene de una página HTTPS, no llevaDomainy usaPath=/. SinDomain, la cookie se devuelve solo al host que la creó, no a sus subdominios.
Lo que ya no conviene enviar
X-XSS-Protection: MDN la marca como obsoleta y advierte que en algunos casos crea vulnerabilidades XSS. OWASP recomienda no enviarla o enviarla en0, y usar CSP.X-Frame-Options: no está obsoleta, peroframe-ancestorsla reemplaza en los navegadores que la soportan. Si envías ambas yframe-ancestorsestá en una política aplicada (no en Report-Only), la especificación CSP indica que se ignoraX-Frame-Options. Puedes mantenerDENYpara navegadores antiguos;ALLOW-FROMya no funciona.Expect-CT: según MDN está prácticamente obsoleta desde junio de 2021, y Chromium la declaró obsoleta desde la versión 107 porque ya exige Certificate Transparency por defecto. OWASP: no la uses.Public-Key-Pins: Chromium la eliminó en 2018 y ningún navegador moderno la soporta, según OWASP. Quítala si aún está.ServeryX-Powered-Bycon versiones: OWASP recomienda quitarX-Powered-Byy dejarServersin datos útiles, recordando que hay otras formas de identificar tu tecnología.
Cómo desplegarlas sin romper el sitio
- Haz inventario de scripts, iframes y ventanas emergentes de terceros.
- Empieza por lo que rara vez rompe algo:
nosniff,Referrer-Policy,Permissions-Policyyframe-ancestors. - Deja la CSP de scripts en Report-Only y revisa los reportes antes de aplicarla.
- Sube HSTS por etapas;
preloades opcional y no se recomienda por defecto. - Prueba errores y redirecciones, no solo la portada.
Nginx
add_header solo actúa en algunos códigos de respuesta (200, 201, 204, 206, 301, 302, 303, 304, 307 y 308) salvo que agregues always. Además, un bloque que define su propio add_header deja de heredar los del nivel superior. Desde la versión 1.29.3, la directiva add_header_inherit permite cambiar esa regla.
server {
listen 443 ssl;
server_name example.com;
server_tokens off; # no muestra la versión de nginx
add_header Strict-Transport-Security "max-age=300; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
location /descargas/ {
# Este add_header anula la herencia: repite aquí las cabeceras anteriores
add_header Cache-Control "no-store" always;
}
}Apache
Con mod_headers, Header set (por defecto onsuccess) y Header always set usan tablas distintas; solo always se aplica a errores y redirecciones generadas por Apache. Para no duplicar cabeceras, OWASP sugiere quitar la versión onsuccess y fijar con always.
<VirtualHost *:443>
ServerName example.com
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=300; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
# X-Powered-By puede quedar en cualquiera de las dos tablas
Header unset X-Powered-By
Header always unset X-Powered-By
</IfModule>
</VirtualHost>
# En la configuración global: Server solo dice "Apache"
ServerTokens ProdCloudflare Pages
El archivo _headers, sin extensión, va en la carpeta de archivos estáticos que se publica; sin framework, directamente en la de salida del build. Admite hasta 100 reglas y 2.000 caracteres por línea, un límite a vigilar con políticas CSP largas. Sus cabeceras no se aplican a las respuestas de Pages Functions: esas se fijan en el código.
/*
Strict-Transport-Security: max-age=300; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: object-src 'none'; base-uri 'self'; frame-ancestors 'none'Cómo comprobarlas
DevTools de Chrome. En Network, selecciona la petición y abre Headers: Response Headers muestra lo que llegó, y ahí puedes sobrescribir una cabecera localmente para probarla antes de desplegarla. El panel Issues reporta violaciones de CSP y problemas de cookies y de COEP.
curl. curl -I pide solo cabeceras con el método HEAD. Para ver las de un GET, en la portada, en una página de error y por HTTP:
curl -s -D - -o /dev/null https://example.com/
curl -s -D - -o /dev/null https://example.com/no-existe
curl -sI http://example.com/ # debe redirigir a HTTPS, sin HSTSHTTP Observatory. Está en developer.mozilla.org/en-US/observatory. Se lanzó en MDN el 2 de julio de 2024 y la antigua dirección observatory.mozilla.org redirige ahí. El historial de escaneos de cada dominio es público y está pensado para sitios, no para API.
securityheaders.com. Sigue en funcionamiento; lo creó Scott Helme y hoy es parte de la empresa Snyk. Califica de A+ a F según cuántas cabeceras de seguridad envías; una R indica que la respuesta fue una redirección.
Ninguna nota dice si tu CSP protege de verdad: eso lo dicen los reportes.
Configuración base comentada
Para un sitio que no se embebe en otros ni necesita aislamiento entre orígenes. Ajústala a tu caso:
# HTTPS por un año, sin preload. Llega aquí después de las etapas de prueba
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Aplicada desde el inicio: sin plugins, sin <base> ajeno y nadie (ni tu propio sitio) puede mostrar tus páginas en un iframe
Content-Security-Policy: object-src 'none'; base-uri 'self'; frame-ancestors 'none'
# En prueba: la política estricta de scripts, con reportes
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'self'; report-to csp; report-uri /csp-reports
# El navegador respeta el Content-Type declarado
X-Content-Type-Options: nosniff
# Solo el origen hacia otros sitios
Referrer-Policy: strict-origin-when-cross-origin
# Funciones que el sitio no usa
Permissions-Policy: camera=(), microphone=(), geolocation=()
# Ventana separada de otros orígenes (same-origin-allow-popups si usas ventanas de pago u OAuth)
Cross-Origin-Opener-Policy: same-origin
# Tus recursos, solo para tu sitio y subdominios
Cross-Origin-Resource-Policy: same-site
# Solo para navegadores antiguos; frame-ancestors tiene prioridad
X-Frame-Options: DENY
# Cookie de sesión
Set-Cookie: __Host-sesion=VALOR; Path=/; Secure; HttpOnly; SameSite=LaxLista de verificación
Revisión | Cabecera |
|---|---|
Presentes también en errores y redirecciones ( | Todas |
En Nginx, cada | Todas |
CSP estricta probada en Report-Only, sin | CSP |
| CSP |
| HSTS |
| HSTS |
|
|
Sesión con | Cookies |
Sin | Obsoletas |
| Todas |
DevTools, | Todas |
Fuentes
- MDN · Content Security Policy (CSP)
- MDN · Content-Security-Policy-Report-Only y Reporting-Endpoints
- W3C · Content Security Policy Level 3 (Working Draft, 27 de octubre de 2026)
- web.dev · Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP)
- web.dev · Security headers quick reference
- OWASP · HTTP Security Response Headers Cheat Sheet
- OWASP · Content Security Policy Cheat Sheet
- OWASP Secure Headers Project
- RFC 6797 · HTTP Strict Transport Security (HSTS)
- MDN · Strict-Transport-Security
- hstspreload.org y eliminación de la lista
- MDN: X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, Cross-Origin-Resource-Policy y Set-Cookie
- MDN: X-XSS-Protection, X-Frame-Options y Expect-CT
- nginx · módulo headers y directiva server tokens
- Apache HTTP Server · módulo headers y ServerTokens
- Cloudflare Pages · Headers
- Chrome DevTools · Network features reference y Issues panel
- curl · manual
- MDN · HTTP Observatory, FAQ y Tests and scoring
- securityheaders.com, About y FAQ
Si quieres revisar las cabeceras de tu sitio y planificar cómo activarlas sin interrumpirlo, conversemos.