Guía de solución

Cómo configurar cabeceras HTTP de seguridad vía .htaccess vs Nginx

20 de mayo de 2026

Las cabeceras HTTP de seguridad son cabeceras de respuesta que le dicen a los navegadores que apliquen comportamientos de seguridad específicos al renderizar una página. Se configuran en el servidor web, no en WordPress, y no cuestan rendimiento al activarse. Un sitio WordPress con las cabeceras correctas bloquea clickjacking, descensos a contenido mixto, exploits de MIME-sniffing y una gran parte de payloads de cross-site scripting. La configuración es un cambio único en el .htaccess de Apache o en la config del servidor de Nginx. Abajo están las siete cabeceras que importan en 2026, con snippets listos para copiar-pegar para ambos servidores web.

¿Qué cabeceras HTTP de seguridad debe establecer un sitio WordPress?

Siete cabeceras cubren el modelo de amenazas realista para un sitio orientado a contenido. En orden de prioridad:

  • Strict-Transport-Security (HSTS). Fuerza a los navegadores a usar HTTPS durante un año (o más) después de la primera visita, previniendo ataques de HTTPS-stripping en WiFi público. Obligatoria si tu sitio tiene usuarios logueados.
  • Content-Security-Policy (CSP). Le dice al navegador exactamente qué fuentes de JavaScript, CSS, imágenes y fuentes están permitidas. La cabecera más impactante contra XSS, pero también la más complicada de configurar sin romper tu sitio.
  • X-Frame-Options o la directiva CSP frame-ancestors. Previene que tu sitio sea embebido en un iframe de otro dominio, bloqueando ataques de clickjacking contra el formulario de login.
  • X-Content-Type-Options: nosniff. Impide que los navegadores adivinen el tipo de contenido de una respuesta, cerrando una clase de ataques de "sube una imagen falsa que en realidad es JavaScript".
  • Referrer-Policy. Controla cuánto de la URL actual se envía en la cabecera Referer en clics salientes. Los defaults filtran query strings a terceros; strict-origin-when-cross-origin es la recomendación moderna.
  • Permissions-Policy (antes Feature-Policy). Deshabilita funciones del navegador que tu sitio no usa (cámara, micrófono, geolocalización), reduciendo la exposición si algún script de terceros intenta acceder a ellas.
  • Cross-Origin-Opener-Policy. Aísla tu sitio de cualquier ventana popup que abra, mitigando ataques de canal lateral tipo Spectre. Requerida para que SharedArrayBuffer funcione.

Lo que ya no se recomienda: X-XSS-Protection (deprecada, los navegadores la ignoran), Public-Key-Pins (deprecada, causó demasiados bloqueos) y Expect-CT (deprecada desde 2023).

Apache .htaccess: bloque copia-pega de cabeceras de seguridad

Añade esto en la parte superior de tu .htaccess en el raíz de WordPress, por encima del bloque de WordPress (# BEGIN WordPress). El guard IfModule asegura que el snippet no se rompa si falta mod_headers.

<IfModule mod_headers.c>
    # HSTS: forzar HTTPS por 1 año, incluir subdominios, listo para preload
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    # Prevenir clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"

    # Bloquear MIME-sniffing
    Header always set X-Content-Type-Options "nosniff"

    # Limitar fuga de referrer
    Header always set Referrer-Policy "strict-origin-when-cross-origin"

    # Deshabilitar funciones del navegador no usadas
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()"

    # Aislamiento cross-origin
    Header always set Cross-Origin-Opener-Policy "same-origin"

    # CSP inicial conservadora; endurecer una vez sepas qué carga tu sitio
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
</IfModule>

La palabra clave always es crítica. Sin ella, las cabeceras solo se envían en respuestas 2xx y 3xx; con ella, las páginas de error y redirecciones también las llevan. Sin always, un atacante que desencadena un error 500 ve una respuesta sin protección.

El módulo mod_headers debe estar habilitado. En hosting compartido normalmente ya lo está. En un Apache autogestionado: sudo a2enmod headers && sudo systemctl reload apache2.

Nginx: bloque copia-pega de cabeceras de seguridad

Añade esto dentro del bloque server { ... } de tu sitio, típicamente en /etc/nginx/sites-available/tusitio.conf:

    # HSTS: forzar HTTPS por 1 año, incluir subdominios, listo para preload
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Prevenir clickjacking
    add_header X-Frame-Options "SAMEORIGIN" always;

    # Bloquear MIME-sniffing
    add_header X-Content-Type-Options "nosniff" always;

    # Limitar fuga de referrer
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Deshabilitar funciones del navegador no usadas
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;

    # Aislamiento cross-origin
    add_header Cross-Origin-Opener-Policy "same-origin" always;

    # CSP inicial conservadora; endurecer una vez sepas qué carga tu sitio
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;

El always al final en Nginx cumple el mismo propósito que en Apache: hace que la cabecera aplique también a respuestas de error. Recarga Nginx tras el cambio: sudo nginx -t && sudo systemctl reload nginx.

La regla de las dos trampas de Nginx add_header

Nginx tiene una peculiaridad que muerde a la gente repetidamente. Dos reglas para interiorizar antes de tocar config de producción:

  • add_header reemplaza, no fusiona. Si estableces cabeceras en el bloque server y de nuevo en un bloque location, las cabeceras del bloque location reemplazan las del bloque server para esa ubicación. No se suman. El síntoma: las cabeceras funcionan en todas partes excepto dentro de una ubicación específica (como /wp-admin/ donde añadiste una regla personalizada).
  • Añade siempre el flag always. Sin él, una página 404 o una página de error 500 se sirve sin tus cabeceras de seguridad. Los atacantes pueden desencadenar errores deliberadamente para evadir protecciones basadas en cabeceras.

El Header always set de Apache es el equivalente al flag always de Nginx y tiene el mismo efecto.

Apache vs Nginx: ¿cuál es más fácil de configurar?

Prácticamente equivalentes para cabeceras de seguridad. Ambos usan una sola directiva por cabecera y aceptan valores de cabecera idénticos. Tres diferencias dignas de conocer:

  • Dónde vive la config. Apache vía .htaccess puede editarse por directorio por cualquiera con acceso FTP a la instalación de WordPress. La config de Nginx vive fuera de la raíz del documento y normalmente requiere SSH más una recarga. Para hosting compartido de WordPress, .htaccess es más práctico; para configuraciones gestionadas con Nginx, la config del servidor es más performante (Nginx no relee reglas tipo .htaccess en cada request, sino que mantiene la config nginx correcta en memoria).
  • Overrides por ubicación. Apache .htaccess hereda automáticamente la configuración del directorio padre. Los bloques location de Nginx necesitan re-declaración explícita si personalizas uno (por la trampa de fusión de arriba).
  • Realidad del hosting. La mayoría del hosting compartido WordPress (Hostinger, IONOS, GoDaddy, hosts cPanel tradicionales) es Apache. La mayoría del hosting WordPress gestionado (Kinsta, WP Engine, Raidboxes, SiteGround, Cloudways) es Nginx. El reparto de mercado ha pasado de mayoría-Apache a aproximadamente 50/50 en los últimos cinco años.

¿Debería añadir cabeceras de seguridad en WordPress en vez de en el servidor web?

Puedes, pero a nivel servidor web es mejor. Existen tres opciones del lado WordPress:

  • Plugin de seguridad (Wordfence, iThemes Security, etc.). Tiene una función "cabeceras de seguridad" que añade las mismas cabeceras desde PHP. Funciona pero añade unos milisegundos por petición. Útil si no tienes acceso al servidor web.
  • Código personalizado de plugin o tema. Un pequeño mu-plugin puede llamar a header() en PHP para emitir cada cabecera. Mismo overhead, mismas salvedades.
  • Config del servidor web (recomendado). Cero overhead de PHP, se aplica a cada respuesta incluyendo errores 404 y archivos estáticos, sobrevive a fallos de WordPress.

El argumento a favor del nivel servidor web: las cabeceras añadidas en PHP no protegen respuestas que esquivan PHP. Si un atacante alcanza un archivo estático directamente (un backup .phps olvidado, un directorio de uploads con archivos HTML), las cabeceras puestas por PHP no se aplican. El servidor web ve cada petición y es la capa correcta.

¿Cómo afino el Content-Security-Policy sin romper mi sitio?

CSP es la cabecera de seguridad más impactante y también la que más probablemente rompa cosas. Un sitio WordPress carga scripts y estilos de docenas de fuentes por defecto: jQuery de /wp-includes, assets del tema, assets de plugins, Google Fonts (si no se auto-hospedan), analytics, YouTube embebido, etc. Una CSP estricta que no liste explícitamente estos las bloqueará y el sitio parecerá roto de formas sutiles (el login falla, la galería no funciona, analytics se para).

El patrón de despliegue en dos fases que funciona:

  1. Empieza en modo report-only. Usa Content-Security-Policy-Report-Only en vez de Content-Security-Policy. El navegador no bloquea nada; solo registra violaciones en la consola de desarrollador. Navega por tu sitio durante unos días, monitoriza qué se habría bloqueado, y añade las fuentes legítimas a tu política.
  2. Cambia a modo enforcing. Una vez que el modo report-only muestre cero violaciones en una navegación normal, cambia el nombre de la cabecera de vuelta a Content-Security-Policy. Ahora las violaciones se bloquean.

Una trampa común: WordPress y muchos plugins usan manejadores de eventos JavaScript inline (onclick="") y bloques <script> inline. Una CSP estricta requiere o bien 'unsafe-inline' (anula la mayor parte de la protección) o nonces para cada script inline (cambios significativos de código). El punto medio pragmático en 2026: mantén 'unsafe-inline' de momento, pero asegura las otras directivas. Las futuras versiones de WordPress se mueven hacia scripts inline amigables con nonces.

¿Cómo verifico que mis cabeceras de seguridad funcionan?

Cuatro métodos, del más rápido al más autoritativo:

  1. Reporte InspectWP. La sección de Seguridad lista cada cabecera que tu sitio establece, su valor, y marca las que faltan o están mal configuradas. Una sola pantalla.
  2. curl desde línea de comandos. curl -I https://tusitio.com imprime las cabeceras de respuesta. Busca las siete cabeceras de arriba; verifica sus valores.
  3. securityheaders.com. Un escáner público gratuito que califica tu sitio (A+ a F) y explica qué cabeceras faltan o son débiles. Estándar de la industria para auditorías de cabeceras.
  4. DevTools del navegador. Abre DevTools (F12), pestaña Network, haz clic en la petición del documento, mira la sección de Response Headers. Útil para ver violaciones CSP en vivo en la pestaña Console.

Errores y trampas comunes

  • Añadir HSTS sin confirmar que HTTPS funciona en todo el sitio. HSTS fuerza a los navegadores a usar HTTPS durante un año. Si tu sitio tiene un certificado roto o una subpágina que solo funciona en HTTP, los usuarios quedan bloqueados. Prueba HTTPS exhaustivamente antes de activar HSTS. Empieza con un max-age corto (tipo 300 segundos) y sube hasta un año.
  • Establecer X-Frame-Options Y CSP frame-ancestors. Ambas funcionan pero frame-ancestors es el equivalente moderno. Establecer ambas es redundante pero inofensivo; los navegadores modernos prefieren frame-ancestors.
  • Olvidar includeSubDomains en HSTS. Si tu sitio principal es HTTPS pero un subdominio aún sirve HTTP, los atacantes pueden pivotar a través del subdominio. includeSubDomains fuerza HTTPS en todas partes bajo tu dominio. Verifica que todos los subdominios son HTTPS antes de añadirlo.
  • Copiar una CSP de un tutorial genérico. Cada sitio WordPress carga assets de terceros diferentes. Una CSP genérica romperá tu sitio. Usa primero el modo report-only.
  • Mezclar cabeceras de CDN y de origen. Si tienes Cloudflare delante, puedes establecer cabeceras en Cloudflare (Page Rules o Transform Rules) o en el origen. Establecer ambas con valores diferentes causa confusión al depurar. Elige un sitio y documéntalo.
  • Olvidar recargar tras editar. Los cambios en Nginx no hacen nada hasta systemctl reload nginx. Los cambios en .htaccess de Apache son instantáneos; los cambios en la config principal de Apache requieren recarga.

¿Qué hay de HTTP/3 y la historia moderna de cabeceras?

HTTP/3 no cambia nada sobre qué cabeceras establecer; la semántica de las cabeceras es idéntica entre HTTP/1.1, HTTP/2 y HTTP/3. Una cabecera que se vuelve relevante en HTTP/3 es Alt-Svc, que anuncia la disponibilidad de HTTP/3 a clientes que llegaron por HTTP/2. La mayoría de servidores web la añaden automáticamente.

El panorama de navegadores también se mueve gradualmente hacia hacer cumplir defaults modernos incluso cuando faltan cabeceras. Chrome y Firefox ahora ponen por defecto strict-origin-when-cross-origin para Referrer-Policy si no se envía cabecera, y asumen cada vez más HTTPS para cualquier sitio que haya sido servido alguna vez por HTTPS. Establecer las cabeceras explícitamente sigue siendo recomendable porque los defaults varían según la versión del navegador y porque los clientes HTTP no-navegador (curl, scripts, herramientas de monitorización) no implementan estos defaults.

Qué verifica InspectWP

La sección de Seguridad de cada reporte InspectWP inspecciona las cabeceras de respuesta de tu documento principal y reporta cuáles de las cabeceras de seguridad estándar están presentes, qué valores tienen, y cuáles faltan o están en valores no recomendados. Falta de HSTS en un sitio HTTPS se marca como advertencia; falta de X-Content-Type-Options se marca como informativo; una CSP establecida a default-src * (efectivamente ninguna política) se marca como mala configuración. El reporte también nota cuándo las cabeceras se establecen a nivel CDN versus a nivel origen, ya que eso afecta qué necesitarías cambiar para actualizarlas. El estado recomendado es las siete cabeceras presentes con valores razonables, y CSP o bien en modo report-only mientras la afinas o en modo enforcing una vez afinada.

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