El bloque
Pégalo al final del .htaccess de la raíz del sitio. Si tu hosting te da acceso al vhost, es mejor sitio todavía: el .htaccess se relee en cada petición y el vhost no.
<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=()"
</IfModule>El envoltorio <IfModule> evita que el sitio devuelva un 500 si mod_headers no está cargado. Es una red de seguridad, no una solución: si el módulo no está, las cabeceras simplemente no se aplican y no te enteras.
Comprueba que mod_headers está activo
Es el motivo número uno de que el bloque no haga nada. Si tienes acceso por SSH:
apachectl -M | grep headersSi devuelve headers_module (shared), está activo. Si no devuelve nada, hay que cargarlo:
a2enmod headers && systemctl reload apache2Si el .htaccess se ignora
Apache solo lee el .htaccess si el vhost lo permite. Si has puesto el bloque, mod_headers está activo y aun así no pasa nada, es esto:
<Directory /var/www/tu-sitio>
AllowOverride All
</Directory>Con AllowOverride None el fichero se ignora por completo, en silencio. En hosting compartido viene permitido por defecto.
Content-Security-Policy, aparte
La CSP no se puede copiar y pegar: depende de qué scripts, estilos y fuentes carga tu sitio. Una CSP copiada rompe la web o no protege nada. El orden correcto es medir primero:
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"Report-Only no bloquea: solo anota en la consola del navegador lo que habría bloqueado. Navega tu sitio unos días, mira la consola, añade a la política los orígenes legítimos que aparezcan, y solo entonces cambia la cabecera a Content-Security-Policy.
Verificar
Desde tu terminal, que es lo más directo y no miente:
curl -sI https://tu-dominio.com | grep -i -E "strict-transport|x-frame|x-content-type|referrer-policy|permissions-policy"Comprueba también una respuesta de error, que es lo que el always arregla:
curl -sI https://tu-dominio.com/esta-ruta-no-existe | grep -i x-frame- Si la segunda orden no devuelve nada, te falta el always.
- Si ninguna devuelve nada, es mod_headers o AllowOverride.
- securityheaders.com/?q=tu-dominio.com te da la misma información con nota de la A a la F, útil para enseñárselo al cliente.