Strict-Transport-Security (HSTS) le dice al browser que tu sitio se accede SIEMPRE por HTTPS. La primera vez que un visitante llega por HTTPS y recibe el header, lo guarda; cualquier intento futuro de cargar el sitio por http:// se reescribe a https:// antes de salir del browser.
Es el control que cierra el último hueco que dejó la migración a HTTPS: el primer http:// que un usuario tipea o que un link viejo apunta. Sin HSTS, ese primer request va en claro y puede ser interceptado para hacer downgrade (TLS stripping).
El problema: HSTS es pegajoso. Una vez que un browser lo memoriza, no hay forma práctica de bajarse rápido si te equivocaste. Por eso vale la pena entender qué activas antes de pegarlo.
Anatomía del header
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Tres piezas, cada una con su trade-off:
max-age=63072000— segundos que el browser memoriza la política. 63072000 = 2 años. Lo mínimo razonable para producción es 6 meses (15552000). Menos de eso es práctica de “estoy probando”.includeSubDomains— extiende la regla a TODOS los subdominios del host actual. Es lo que sube la postura de “tu apex está protegido” a “tu zona DNS entera está protegida”.preload— declara intención de aparecer en la HSTS preload list. Los browsers traen esa lista hardcoded; tu sitio queda protegido antes del primer request. Es la única forma de cerrar el “primer-request gap” del 100%.
La trampa de includeSubDomains
Este es el campo que más se equivoca. Implica que cualquier subdominio bajo tu zona también va a forzarse a HTTPS.
Escenario típico que rompe sitios en producción:
organizacion-ejemplo.clestá en HTTPS con cert válido. Se le activa HSTS conincludeSubDomains.- La misma zona tiene un
legacy.organizacion-ejemplo.clviejo, sin cert, sirviendo un sitio interno por HTTP plano (intranet, monitoreo Grafana, lo que sea). - A partir del primer visit a
organizacion-ejemplo.cl, ese mismo browser ya no puede entrar alegacy.organizacion-ejemplo.clpor HTTP. Y como no tiene cert TLS, tampoco por HTTPS. El subdominio queda inalcanzable hasta que el max-age expire.
La lógica defensiva es la correcta — un atacante que controle DNS del subdominio podría exfiltrar cookies del apex si HSTS no aplicara a subs. Pero en práctica te tomas el tiempo de inventariar todos tus subs antes de activar la regla.
El camino seguro: aumentar gradualmente
La receta recomendada:
Paso 1 — max-age corto, sin includeSubDomains, sin preload
Strict-Transport-Security: max-age=300
Cinco minutos. Si rompes algo, en 5 minutos los browsers olvidan. Lo dejas 24-48 horas en producción para verificar que el apex realmente sirve TLS bien (no hay request mixto, no hay redirect-loop, todo funciona).
Paso 2 — max-age 6 meses, sin includeSubDomains todavía
Strict-Transport-Security: max-age=15552000
Lo dejas 1-2 semanas. Esto ya te da la protección real para el apex.
Paso 3 — Inventario de subdominios + decisión sobre includeSubDomains
Antes de agregarlo, listá todos tus subs:
dig +short NS organizacion-ejemplo.cl
dig +short ANY organizacion-ejemplo.cl
# o consulta los CT logs:
curl -s 'https://crt.sh/?q=%25.organizacion-ejemplo.cl&output=json' | jq -r '.[].name_value' | sort -u
Para cada uno, verifica:
- ¿Sirve HTTPS con cert válido?
- ¿Es un subdominio “vivo” o queda DNS huérfano? (los huérfanos no importan para HSTS, pero deberías limpiarlos por otras razones).
- ¿Hay alguno que SÓLO sirva HTTP por diseño? (intranets viejas, dashboards internos sin TLS).
Si todos sirven HTTPS bien, agregas includeSubDomains. Si hay alguno HTTP-only, primero lo migras a HTTPS o lo retiras del DNS.
Strict-Transport-Security: max-age=15552000; includeSubDomains
Una semana más en este estado.
Paso 4 — max-age 2 años + preload
Cuando ya estás seguro de que todo el ecosistema soporta TLS y quieres cerrar el primer-request gap:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Submitís el dominio en hstspreload.org. Toma 1-3 meses entrar en la próxima release de Chromium, después Firefox/Safari/Edge la heredan.
Importante: una vez en la preload list, salir es lento. Solicitar la remoción puede tomar meses y los browsers viejos pueden quedarte preloaded por años. No actives preload mientras dudes.
Config en Apache
Vía .htaccess o vhost
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</IfModule>
El flag always es importante: sin él, Apache aplica el header solo en respuestas 200; con always también lo manda en 30x/40x/50x, donde tradicionalmente se “olvida” — y es justamente en un 301 HTTP→HTTPS donde más útil resulta.
Redirect 301 obligatorio antes
HSTS asume que tu sitio responde por HTTPS. El redirect HTTP→HTTPS debe estar antes:
<VirtualHost *:80>
ServerName organizacion-ejemplo.cl
ServerAlias www.organizacion-ejemplo.cl
Redirect permanent / https://organizacion-ejemplo.cl/
</VirtualHost>
O en .htaccess:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
El header HSTS debe servirse desde HTTPS (los browsers ignoran HSTS recibido por HTTP), así que el vhost :443 es el que lleva el Header always set.
Detrás de Cloudflare
Si estás detrás de CF (o cualquier CDN), tienes dos opciones para servir HSTS:
- Del origen: Apache pone el header, CF lo deja pasar. Pro: una sola fuente de verdad. Contra: si tu origen alguna vez se sirve directo (CF bypass), el header sigue saliendo.
- Del edge (CF dashboard): CF inyecta el header. Pro: aunque tu origen se sirva en HTTP por algún motivo, CF te cubre. Contra: si migras de CDN o quitas CF, pierdes el header sin enterarte.
En la práctica, ambos: el origen lo manda Y CF lo refuerza. Cloudflare detecta duplicados y deja uno solo.
Cómo verificas
Antes de pegarlo en producción
# verifica que el sitio sirve HTTPS bien
curl -sI https://organizacion-ejemplo.cl/ | grep -i "strict-transport"
# verifica que ningún subdominio queda HTTP-only:
for sub in www app api blog dashboard mail; do
echo "--- $sub.organizacion-ejemplo.cl ---"
curl -sI --max-time 5 https://$sub.organizacion-ejemplo.cl/ 2>&1 | head -1
done
Después de pegarlo
# debería verse el header completo
curl -sI https://organizacion-ejemplo.cl/ | grep -i "strict-transport"
# Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
# verifica que el redirect 301 desde HTTP funciona
curl -sI http://organizacion-ejemplo.cl/ | head -5
# debería devolver 301 hacia https://...
Antes de submitir a preload
hstspreload.org hace los checks por ti: header presente, max-age suficiente, includeSubDomains, preload, redirect 301, cert válido, subdominios accesibles por HTTPS. Si fallas algún check, te dice cuál — y mejor arreglarlo antes que después.
Salida de emergencia
Si pegaste HSTS y quieres revertir:
- Antes de preload: bajá
max-agea 0 (no quites el header). Los browsers que recibanmax-age=0borran la entrada. Tardásmax-ageoriginal en cubrir a todos los visitantes que tuvieron la versión anterior. - Después de preload: hay que solicitar remoción en la HSTS preload list + esperar la siguiente release de Chrome (~6 semanas) + heredan los demás browsers (~3 meses) + los browsers viejos pueden quedarte protegidos por años. No hay rollback rápido.
Cómo lo detectamos en el FreeScan
El Asentic FreeScan reporta tres patrones distintos:
- HSTS ausente — Medium. El sitio funciona en HTTPS pero no protege el primer request.
- HSTS con max-age corto — Low. El header está, pero
max-age < 6 meseste deja una ventana de exposición innecesaria. - HSTS sin
includeSubDomainso sinpreload— Info. Eligible para subir la postura, no es un riesgo per se.
La distinción importa porque “no implementado” y “implementado pobre” se ven igual en una auditoría superficial, pero requieren acciones distintas.
Cierre
HSTS es de los headers con mejor ratio efecto/esfuerzo. La parte difícil no es el header en sí — son tres líneas de Apache — sino el inventario de subdominios que hay que hacer antes para no romper el ecosistema. Dedicarle un día a eso y migrar gradualmente vale el costo de la postura final.
Si tu sitio ya sirve HTTPS hace tiempo, lo más probable es que el camino al preload esté a una semana de distancia.