Qué cabeceras tienen sentido
- Strict-Transport-Security (HSTS): los navegadores solo cargan la web por HTTPS.
- Content-Security-Policy (CSP): define de dónde pueden venir scripts, estilos y marcos. La protección más eficaz contra XSS, pero también la más laboriosa.
- X-Content-Type-Options: nosniff: impide que los navegadores adivinen el tipo de archivo.
- Referrer-Policy: limita lo que se envía al pulsar enlaces a otras webs.
- Permissions-Policy: desactiva cámara, micrófono, ubicación, etc., si no los necesitas.
- frame-ancestors (en la CSP) o X-Frame-Options: protección contra clickjacking.
X-XSS-Protection ya no hace falta. Los navegadores modernos la ignoran y en los antiguos podía incluso perjudicar.
nginx
# /etc/nginx/snippets/security-headers.conf add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always; # en el bloque server: include snippets/security-headers.conf; server_tokens off;
nginx tiene dos trampas:
- Sin
alwayslas cabeceras faltan en páginas de error y redirecciones (p. ej. 404, 301). - Sin herencia: en cuanto un bloque
locationtiene su propioadd_header, las cabeceras del bloqueserverdejan de aplicarse ahí. Por eso incluye el archivo conincludetambién en esos bloqueslocation.
Apache
# sudo a2enmod headers
<VirtualHost *:443>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
Header always unset X-Powered-By
</VirtualHost>
# global, p. ej. en security.conf
ServerTokens Prod
ServerSignature Off
En un alojamiento compartido sin acceso a la configuración del servidor, las mismas líneas Header suelen funcionar en el .htaccess de la raíz. La versión de PHP en la cabecera X-Powered-By se desactiva con expose_php = Off en php.ini.
Implantar la CSP sin romper la web
Una CSP demasiado estricta bloquea tus propios scripts, vídeos incrustados o herramientas de analítica. Así que por etapas:
- Primero como
Content-Security-Policy-Report-Only. El navegador no bloquea nada, pero muestra las infracciones en la consola. - Revisar la consola en las páginas importantes y añadir las fuentes necesarias, p. ej.
script-src 'self' https://cdn.example.net. - Mover los scripts en línea a archivos o darles un nonce.
'unsafe-inline'enscript-srcanula en gran parte la protección contra XSS. - Solo entonces cambiar a
Content-Security-Policy.
Cuidado con HSTS
HSTS no se puede retirar sin más: los navegadores lo recuerdan durante el tiempo indicado. Empieza con max-age=300, comprueba que de verdad todos los subdominios admiten HTTPS y sube luego a dos años. includeSubDomains afecta también a nombres internos como intranet.example.com si están bajo el mismo dominio.
Comprobar
curl -sI https://example.com curl -sI https://example.com/no-existe # ¿también en 404?
El comprobador de cabeceras de seguridad valora todas las cabeceras, detecta errores típicos como 'unsafe-inline' y genera las líneas adecuadas para tu servidor.
FAQ
¿Basta X-Frame-Options o necesito frame-ancestors?
frame-ancestors en la CSP es la forma moderna y admite varias fuentes. Para navegadores muy antiguos, añadir X-Frame-Options: SAMEORIGIN no hace daño.
¿Por qué veo las cabeceras duplicadas?
Normalmente las ponen tanto la aplicación (p. ej. PHP o un CMS) como el servidor web. Si hay dos CSP se aplican ambas y gana la más estricta. Mejor ponerlas en un solo sitio.
¿Necesitan las API cabeceras de seguridad?
Sí, al menos HSTS, nosniff y una CSP estricta como default-src 'none'; frame-ancestors 'none'. No cuestan nada e impiden que las respuestas se usen de forma indebida en el navegador.
¿Pone la CDN las cabeceras por mí?
Solo si lo configuras allí. Muchas CDN simplemente pasan las cabeceras del servidor de origen.