Caddy, HSTS y security.txt
Módulo 7: Dominio, servidor web y https · Tiempo de lectura: 12 minutos
Objetivo de aprendizaje: Después de esta lección podrá usar Caddy como servidor web sencillo con https automático, configurar HSTS y las cabeceras de seguridad más importantes (en Caddy y nginx) sin dejar su sitio inaccesible, y publicar un security.txt válido.
Conceptos clave: Caddy, Caddyfile, https automático, reverse_proxy, file_server, header, HSTS (Strict-Transport-Security), max-age, includeSubDomains, preload, X-Content-Type-Options, frame-ancestors, Referrer-Policy, security.txt, Contact, Expires, /.well-known/
Caddy: https sin preocupaciones
En las lecciones 7.2 y 7.3 creó con nginx y certbot un sitio con https. Son dos programas, unos cuantos archivos y un temporizador. Caddy lo hace todo en uno: es un servidor web con un cliente ACME integrado. Si un nombre figura en la configuración, apunta al servidor y los puertos 80 y 443 son accesibles, Caddy obtiene por sí mismo un certificado, lo renueva a tiempo y redirige http a https.
Caddy está en el repositorio de paquetes de Debian y Ubuntu (en una versión algo más antigua); sus creadores también ofrecen un repositorio apt propio con la versión más reciente, con su propia clave y Signed-By, como en la lección 4.1. En el módulo 9, Caddy se ejecuta como contenedor para sus servicios Docker (lección 9.4). Aquí lo usa como un servicio normal.
anna@web1:~$ sudo apt install caddy
anna@web1:~$ systemctl status caddyUse nginx o Caddy en los puertos 80 y 443, pero no ambos: solo un programa puede escuchar en un puerto.
El Caddyfile
La configuración está en /etc/caddy/Caddyfile. Un sitio estático y una aplicación detrás de un proxy inverso:
example.com, www.example.com {
root * /var/www/example.com
file_server
encode zstd gzip
}
app.example.com {
reverse_proxy 127.0.0.1:3000
}Eso es todo. Caddy escucha automáticamente en IPv4 e IPv6, solicita certificados para los tres nombres, redirige http a https y, con reverse_proxy, transmite por sí mismo las cabeceras X-Forwarded-For y X-Forwarded-Proto (lo que en nginx hizo a mano, lección 7.2).
Probar y recargar funciona igual que con nginx:
anna@web1:~$ caddy validate --config /etc/caddy/Caddyfile
Valid configuration
anna@web1:~$ sudo systemctl reload caddy
anna@web1:~$ sudo journalctl -u caddy -fEn el journal verá cómo se obtienen los certificados. Si algo falla, allí aparece el motivo: normalmente el mismo que con certbot (puerto 80 cerrado, el nombre apunta a otro sitio, IPv6 roto; lección 7.3).
HSTS: que el navegador recuerde que es https
Redirigir de http a https tiene un momento débil: la primerísima petición por http todavía viaja sin cifrar. Alguien en la misma red puede interceptarla y mantener al visitante en http. HSTS (HTTP Strict Transport Security, RFC 6797) lo soluciona. Su sitio envía por https esta cabecera:
Strict-Transport-Security: max-age=31536000El navegador recuerda entonces durante un año (31.536.000 segundos) que a este nombre solo se puede acceder por https. Si más adelante el visitante escribe http://, el navegador lo convierte por sí mismo en https, antes de que nada pase por la red. El navegador ignora una cabecera HSTS que llega por http; solo cuenta por https.
Tiene una contrapartida. Si un navegador ha memorizado HSTS y ve un error de certificado (caducado, nombre incorrecto), el visitante no recibe ningún botón para continuar de todos modos. Su sitio queda entonces inaccesible para esos visitantes hasta que el certificado sea correcto. Por eso:
- Active HSTS solo cuando https funcione en todas partes y la renovación sea automática (lección 7.3).
- Empiece con un
max-agecorto, por ejemplo un día (86400), y auméntelo por etapas hasta un año. - Tenga cuidado con
includeSubDomains: se aplica a todos los subdominios, también a un subdominio antiguo con un dispositivo o servicio sin certificado válido. preloadsolicita la inclusión en una lista integrada en los navegadores (hstspreload.org). Para ello exigen, entre otras cosas, unmax-agede al menos un año,includeSubDomainsy https en todos los subdominios. Salir de la lista tarda meses. Hágalo solo si está seguro de que todo el dominio será https para siempre.
Las demás cabeceras de seguridad
Además de HSTS, hay unas cuantas cabeceras que ayudan mucho con poco esfuerzo:
| Cabecera | Contra qué protege | Valor para empezar |
|---|---|---|
X-Content-Type-Options | el navegador intenta adivinar qué es un archivo, de modo que una "imagen" subida puede ejecutarse como script | nosniff |
Content-Security-Policy: frame-ancestors (o la más antigua X-Frame-Options) | clickjacking: otro sitio carga su página de forma invisible en un marco | frame-ancestors 'self' (o X-Frame-Options: SAMEORIGIN) |
Referrer-Policy | la dirección de su página (con términos de búsqueda o números de pedido) se filtra a otros sitios | strict-origin-when-cross-origin |
Un Content-Security-Policy completo (qué scripts y estilos pueden cargarse) se hace a medida para cada sitio y queda fuera de este curso; empiece solo con frame-ancestors.
En Caddy las configura con la directiva header dentro del bloque de un sitio:
example.com, www.example.com {
root * /var/www/example.com
file_server
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Content-Security-Policy "frame-ancestors 'self'"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}Caddy no configura HSTS ni estas cabeceras por sí mismo; por tanto, tiene que añadirlas usted. -Server elimina la cabecera Server. En la lección 9.4 añadirá en el mismo tipo de bloque su propia cabecera X-Weboke-Cursus para el ejercicio.
En nginx pone las mismas líneas en el bloque server del puerto 443:
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;always hace que también se envíen con las páginas de error. Cuidado con una trampa conocida de nginx: si dentro de un bloque location hay un solo add_header propio, las líneas add_header del bloque server dejan de aplicarse a esa location. Por eso, compruebe siempre el resultado:
anna@laptop:~$ curl -sI https://example.com | grep -iE 'strict|nosniff|frame|referrer'
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
content-security-policy: frame-ancestors 'self'
referrer-policy: strict-origin-when-cross-originsecurity.txt: así pueden avisarle de una vulnerabilidad
Supongamos que alguien encuentra una vulnerabilidad en su servidor, como una carpeta abierta o un servicio desactualizado. ¿Cómo sabe esa persona a quién avisar? Para eso existe security.txt (RFC 9116): un pequeño archivo de texto en una ubicación fija, https://<jouwdomein>/.well-known/security.txt.
Contact: mailto:security@example.com
Expires: 2027-10-31T23:00:00Z
Preferred-Languages: nl, en
Canonical: https://example.com/.well-known/security.txtLas reglas:
Contactes obligatorio: una direcciónmailto:,https://otel:a la que lleguen los avisos. Use una dirección que alguien lea de verdad.Expireses obligatorio y solo puede aparecer una vez: la fecha a partir de la cual el archivo se considera obsoleto. El RFC recomienda una fecha dentro de un año. Así no queda publicado un archivo con una dirección olvidada hace tiempo. Anote en su agenda cuándo debe renovarlo.Preferred-Languages,CanonicalyPolicy(un enlace a su política de avisos) son opcionales.- El archivo debe servirse por https, como texto plano (
text/plain, UTF-8). La ubicación antigua/security.txtse permite como extra, pero/.well-known/tiene prioridad.
Colocarlo en un sitio estático:
anna@web1:~$ sudo mkdir -p /var/www/example.com/.well-known
anna@web1:~$ sudoedit /var/www/example.com/.well-known/security.txt
anna@laptop:~$ curl -sI https://example.com/.well-known/security.txt | grep -i content-type
content-type: text/plain; charset=utf-8Si en nginx usa la regla general location ~ /\. { deny all; } de la lección 7.2, añada antes una excepción: location ^~ /.well-known/ { }. Para una aplicación detrás de un proxy inverso, en Caddy puede hacer que la ruta /.well-known/security.txt apunte a un archivo con un bloque handle y file_server.
Un error frecuente: un sitio o una aplicación que responde a cualquier ruta desconocida con la página principal y el código 200. Entonces security.txt "existe", pero es HTML. Compruebe, por tanto, también el contenido.
En AlmaLinux y Rocky Linux
Allí Caddy se obtiene del repositorio de sus creadores o de EPEL/COPR. Igual que con nginx, SELinux debe permitir un proxy inverso (setsebool -P httpd_can_network_connect 1). El Caddyfile y las cabeceras son los mismos.
Resumen
- Caddy se encarga por sí mismo de los certificados, su renovación y la redirección a https;
reverse_proxytransmite automáticamente las cabeceras correctas. - HSTS hace que los navegadores recuerden que su sitio es solo https; aumente
max-agepoco a poco, tenga cuidado conincludeSubDomainsy mucho cuidado conpreload. - Configure además
X-Content-Type-Options: nosniff,frame-ancestors 'self'y unaReferrer-Policy; en Caddy conheader, en nginx conadd_header ... always, y compruébelo concurl -I. - security.txt está en
/.well-known/security.txt, por https, como texto plano, conContactobligatorio y un únicoExpiresdentro de un año.
Cuestionario
Ejercicio
Ejercicio Z7.4: HSTS, cabeceras y security.txt. Configure para su sitio HSTS (al menos 180 días, después de que https lleve un tiempo funcionando bien), X-Content-Type-Options: nosniff y frame-ancestors 'self' (o X-Frame-Options), y publique un security.txt con Contact y un Expires dentro de un año. Introduzca su nombre de host. El programa obtiene https://<hostnaam>/ y comprueba las cabeceras, y obtiene https://<hostnaam>/.well-known/security.txt y comprueba los campos. No guarda nada salvo el resultado y la fecha de Expires.
Para saber más
- HSTS y cabeceras de seguridad para su sitio web
- Caddy, Automatic HTTPS: https://caddyserver.com/docs/automatic-https
- Caddy, directiva header: https://caddyserver.com/docs/caddyfile/directives/header
- RFC 6797, HTTP Strict Transport Security: https://www.rfc-editor.org/rfc/rfc6797.html
- Lista de precarga HSTS y sus requisitos: https://hstspreload.org/
- RFC 9116, security.txt: https://www.rfc-editor.org/rfc/rfc9116.html
- securitytxt.org (crear y comprobar): https://securitytxt.org/
