Redes

NTP seguro con chrony: NTS, certificados y control de deriva para evitar ataques de reloj

Hay una dependencia que casi nadie auditaba en sus servidores hasta que algo se rompió: la hora. No la hora del servidor NTP, sino la hora de todos los que confían en él. Un reloj desviado few minutos no parece un ataque, pero invalida la validación de certificados TLS, rompe la renovación de Kerberos, hace fallar las firmas de DNSSEC y expira sesiones de tokens en cascada.

En mi homelab el problema no fue un atacante: fue un servidor con RTC dead que arrancaba con la hora de 2023, y el certificado wildcard del proxy inverso dejó de validar para todos los servicios que tenía detrás. Desde ahí que la tratara como un control de seguridad y no como un detalle de configuración.

Este post es el que me hubiera ahorrado esa tarde: chrony con NTS para que solo acepte fuentes autenticadas, los límites de deriva que impiden que un desvío grande se propague, y los comandos para verificar que la autenticación está activa de verdad, no solo que el reloj dice la hora correcta.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 20-30 minutos por host: instalar chrony, escribir /etc/chrony/chrony.conf con las cinco fuentes NTS de ejemplo, un chronyc reload y la verificación con chronyc -N authdata.
  • Requisitos: chrony 4.2 o superior para NTS con GnuTLS, salida a 4460/TCP hacia el servidor NTS-KE, y root para escribir la configuración y recargar el servicio.
  • Resultado: las fuentes aparecen con Mode = NTS y KeyID distinto de cero en chronyc -N authdata; una fuente sin autenticar ya no puede mover el reloj, y maxchange 100 0 0 detiene chronyd ante un salto de más de 100 segundos.
  • Troubleshooting: el fallo más común es un firewall que abre 123/UDP pero bloquea 4460/TCP: el reloj se sincroniza igual, pero sin NTS y sin aviso en el log del cliente.

Por qué el reloj es un objetivo de ataque

El protocolo NTP es simple por diseño: un cliente UDP pregunta la hora a un servidor y acepta la respuesta. Ese diseño, heredado de una era sin criptografía, abre una puerta que un atacante en la ruta puede cruzar sin mucha dificultad.

El manual de chrony describe las operaciones de un atacante en ruta con precisión notable, y esa lista es la definición exacta del problema:

  • Fabricar o modificar respuestas del servidor para crear un desfase arbitrario de tiempo y frecuencia, hacer que el servidor parezca más preciso o insertar un segundo intercalado.
  • Retrasar las peticiones o las respuestas para crear un desfase limitado, acotado por el valor de maxdelay.
  • Descartar peticiones o respuestas para congelar el reloj con la última medición conocida.
  • Redirigir la petición a otro servidor controlado por el atacante.

Y lo más importante: los ataques se combinan. La receta del manual es hacer primero un desfase de frecuencia con paquetes retrasados y después soltar todos los paquetes siguientes, para que el reloj se desvíe rápido y de forma sostenida mientras la señal se ve normal.

El impacto no es teórico. La FAQ de chrony lo enumera: un cliente NTP es susceptible a ataques que rompen las aplicaciones y protocolos que dependen de la exactitud del reloj. DNSSEC, Kerberos, TLS y WireGuard están en la lista. Eso no es “el reloj muestra la hora incorrecta”: es “TLS deja de validar, Kerberos deja de autenticar y DNSSEC deja de resolver”.

Qué es NTS y qué protege

NTS (Network Time Security) es el mecanismo de autenticación del protocolo NTP, definido en la RFC 8915. Resuelve el problema con una clave simétrica compartida y dos canales distintos:

  1. NTS-KE (Key Establishment): una conexión TLS, con su puerto propio en 4460/TCP, donde el cliente se autentica ante el servidor y obtiene las claves y las cookies.
  2. NTP con autenticación AEAD: los paquetes de NTP viajan por el puerto normal 123/UDP, pero cada uno lleva una etiqueta de autenticación que un atacante en ruta no puede fabricar sin la clave.

Los certificados del título de este artículo son los del canal NTS-KE: los certificados TLS que verifican al servidor NTS-KE. No son certificados TLS de servidores web. Esa distinción importa porque el vector de ataque es el establecimiento de claves, no el tráfico web.

La parte que muchos pasan por alto: NTS no cifra el tráfico NTP. Solo autentica. El atacante todavía puede retrasar o tirar paquetes, que es justamente lo que cubren maxdelay, maxchange y maxdrift.

Una aclaración importante de cronología, porque es el error más común al escribir sobre este tema: NTS no es una novedad de chrony 4.9. Se introdujo en chrony 4.0, el 7 de octubre de 2020, junto con authselectmode, ntsdumpdir, confdir, sourcedir, clockprecision y dscp. Lo que hizo 4.9, publicado el 27 de agosto de 2026, fue mejorar el entorno de NTS con la opción maxntsretry en las directivas server y pool, que limita el intervalo máximo entre reintentos de NTS-KE para no martillar al servidor cuando no responde.

Versión Fecha Qué trae para este tema
4.0 7 oct 2020 NTS con claves AES-CMAC, authselectmode, ntsdumpdir
4.1 13 may 2021 Servidores NTS por IP validando el SAN del certificado, certificados de confianza por fuente
4.2 16 dic 2021 AES-CMAC y funciones hash en GnuTLS
4.3 31 ago 2022 maxdelayquant reemplaza el filtro maxdelaydevratio
4.4 9 ago 2023 AES-GCM-SIV (con Nettle 3.9 o superior) para acortar las cookies NTS
4.5 5 dic 2023 AES-GCM-SIV en GnuTLS, activación por socket de systemd
4.6 2 sep 2024 Opción kod en ratelimit, directiva leapseclist
4.6.1 8 oct 2024 Directiva ntsaeads para elegir algoritmo AEAD
4.7 11 jun 2025 Se deja de soportar NTS con Nettle menor a 3.6 y GnuTLS menor a 3.6.14
4.8 27 ago 2025 maxunreach, chronyc -u para soltar privilegios de root
4.9 27 ago 2026 maxntsretry, maxtxbuffers, minstratum y maxstratum

Instalar chrony y revisar la configuración por defecto

Antes de cambiar nada, mira qué hay instalado y qué dejó configurado el sistema. En Debian y Ubuntu el paquete se llama chrony; en Fedora, RHEL y derivados también.

Este comando instala el daemon, lo habilita al arranque y te muestra qué versión tienes, que es el dato que decide si puedes usar maxntsretry o ntsaeads:

sudo apt install chrony
sudo systemctl enable --now chrony
chronyd -v

chronyd -v imprime la versión del daemon. Si devuelve 4.2 o superior, tienes NTS completo con GnuTLS. Por debajo de 4.2 conviene actualizar antes de seguir, porque el soporte de NTS con GnuTLS llegó justo ahí.

El segundo paso es ver la configuración que el paquete te dejó, completa con los archivos incluidos:

sudo chronyd -p

chronyd -p imprime la configuración cargada y sale, incluyendo los archivos que específicamente él include o confdir. Es el equivalente a un cat de todos los fragmentos, y la forma correcta de confirmar qué está aplicando el daemon de verdad. Ojo: chronyc -p no hace esto; en chronyc la opción -p es el puerto del socket de comandos.

Si la distribución ya trae NTS configurado, no toques nada todavía. Ubuntu 25.04 (Plucky Puffin) habilitó NTS por defecto en chrony, y desde Ubuntu 25.10 (Questing Quokka) el paquete chrony viene instalado por defecto en lugar de systemd-timesyncd. La configuración de Canonical vive en /etc/chrony/sources.d/ubuntu-ntp-pools.sources y los certificados de confianza del servidor de arranque están en /etc/chrony/conf.d/ubuntu-nts.conf. Puedes confirmarlo mirando los pools que usa:

grep -r "ntp.ubuntu.com" /etc/chrony/sources.d/ /etc/chrony/conf.d/ 2>/dev/null

El archivo de Ubuntu trae además una quinta línea que vale la pena conocer, porque resuelve exactamente el problema del huevo y la gallina del que hablo más abajo:

pool 1.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 2.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 3.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 4.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool ntp-bootstrap.ubuntu.com iburst maxsources 1 nts certset 1

El ntp-bootstrap.ubuntu.com es el servidor de arranque: permite que un sistema sin reloj de hardware, o con un desfase inicial grande, siga pudiendo corregir la hora de forma segura. Si esa búsqueda devuelve los pools de Ubuntu con nts, la parte autenticada ya está hecha por Canonical y tu trabajo real es verificar que funciona y añadir los límites de deriva.

Configurar NTS con fuentes autenticadas

La configuración de un cliente con NTS tiene cuatro piezas: las fuentes, la política de selección, los certificados de confianza y el directorio de cookies.

Este es el esqueleto, con los valores que recomienda la propia FAQ de chrony como ejemplo de cliente que limita el impacto de los ataques:

sudo tee /etc/chrony/chrony.conf > /dev/null <<'EOF'
server ntp1.example.net iburst nts maxdelay 0.1
server ntp2.example.net iburst nts maxdelay 0.2
server ntp3.example.net iburst nts maxdelay 0.05
server ntp4.example.net iburst nts maxdelay 0.1
server ntp5.example.net iburst nts maxdelay 0.1
minsources 3
maxchange 100 0 0
makestep 0.001 1
maxdrift 100
maxslewrate 100
driftfile /var/lib/chrony/drift
ntsdumpdir /var/lib/chrony
rtcsync
EOF
sudo chronyc reload

Cuatro de estas directivas hacen casi todo el trabajo:

  • minsources 3: el reloj no se actualiza hasta que al menos 3 fuentes son seleccionables y coinciden entre sí. Con una sola fuente alcanzable, que podría estar mintiendo, el reloj simplemente se queda quieto. El valor por defecto es 1, es decir, sin protección.
  • maxchange 100 0 0: el máximo desfase aceptado en una actualización, en segundos. El segundo argumento es después de cuántas actualizaciones se activa la comprobación (0 = desde la primera), y el tercero cuántas veces se ignora un desfase grande antes de rendirse (0 = a la primera, chronyd sale con error). Con estos valores, un salto de más de 100 segundos detiene el daemon en lugar de aceptarlo.
  • maxdrift 100: la deriva de frecuencia máxima asumida, en ppm. Un reloj de PC normal deriva menos de 100 ppm, así que el valor de la FAQ es holgado pero suficiente. El valor por defecto de chrony es 100000 ppm, que en la práctica deja la corrección en manos del driver del kernel.
  • maxslewrate 100: la velocidad máxima a la que el reloj puede ajustar. Un slew de 100 ppm corrige un desfase de un segundo en unas tres horas; es lento a propósito.

makestep 0.001 1 permite un salto pequeño (1 milisegundo) únicamente en la primera actualización, para arrancar bien sin que un atacante pueda usar el step para mover el_clock.

ntsdumpdir /var/lib/chrony guarda las cookies NTS recibidas para no repetir el handshake NTS-KE en cada reinicio. Sin esta directiva, chrony no guarda cookies por defecto y cada arranque paga el handshake completo.

Una nota sobre maxchange que vale la pena interiorizar: cuando el límite se dispara y chronyd sale, no se recupera solo. El manual es explícito: hay que reiniciarlo a mano y no debe ser el gestor de servicios quien lo levante automáticamente, porque un reinicio automático equivale a darle al atacante otra oportunidad. Si vas a activar maxchange en producción, configura el servicio para que no se levante solo tras un fallo por este motivo.

Verificar que NTS está funcionando de verdad

Este es el paso que separa “creo que configuré NTS” de “NTS está autenticando mis fuentes”. El comando es chronyc -N authdata:

sudo chronyc -N authdata

La salida tiene esta forma:

Name/IP address             Mode KeyID Type KLen Last Atmp  NAK Cook CLen
=========================================================================
ntp1.example.net             NTS     1   15  256 135m    0    0    8  100
ntp2.example.net             NTS     1   15  256 135m    0    0    8  100
ntp3.example.net                -     0    0    0    -    0    0    0    0

Cómo leerlo:

  • La columna Mode debe decir NTS para cada fuente que querías autenticar. Si una fuente aparece con -, no se ha establecido autenticación con ella.
  • KeyID, Type y KLen deben ser distintos de cero. Cero en estas columnas significa que el establecimiento de claves falló.
  • La columna Type es el identificador del algoritmo AEAD: 15 es AES-SIV-CMAC-256 y 30 es AES-128-GCM-SIV. El 30 es el preferido porque produce cookies más cortas, que es lo que evita que un NTP largo sea bloqueado en redes que limitan ese tamaño. Puedes fijar la lista de algoritmos con ntsaeads, que llegó en 4.6.1.

El -N de chronyc hace que se muestren los nombres de host originales configurados en lugar de los nombres resueltos por DNS inverso. Sin él, el nombre que ves es el del DNS inverso y puede no ser el que escribiste en la configuración, y buscar “por qué mi fuente no aparece” en el nombre equivocado es una pérdida de tiempo clásica.

Para el estado general del reloj y de las fuentes, estos dos son los que uso:

sudo chronyc tracking
sudo chronyc sources -v

En tracking, el campo Leap status debe decir Normal y en sources -v la fuente seleccionada debe tener el estado * en la columna S de la primera tabla. Si Leap status dice Not synchronised, el reloj no está sincronizado aunque el servicio esté corriendo.

Servir NTP en tu LAN con control de acceso

Si además de cliente quieres servir hora a otros equipos de la red, que es el patrón recomendado para un homelab con un servidor sincronizado con NTS y el resto apuntando a él, hay dos cosas que activar en el servidor.

El primero es allow, que por defecto está en cero: sin él, chronyd opera únicamente como cliente y no responde a nadie.

sudo tee /etc/chrony/chrony-server.conf > /dev/null <<'EOF'
allow 192.168.1.0/24
allow 2001:db8::/32
ratelimit interval 1 burst 16
ntsservercert /etc/chrony/nts-server.crt
ntsserverkey /etc/chrony/nts-server.key
EOF
sudo chronyc reload

allow acepta también allow 3.4.5.0/24, allow 0/0 (todo IPv4) o allow a secas (todo, IPv4 e IPv6). Usa siempre la subred concreta: un allow sin argumento en un servidor con IP de cara a internet es exactamente el escenario de amplificación que detrás de CVE-2013-5211.

El segundo es ratelimit, que limita la tasa de respuesta por IP para que un cliente mal configurado o un atacante no ahogue tu servidor. El ejemplo de la documentación (ratelimit interval 1 burst 16) reduce la tasa de respuesta en hasta un 75 % para IPs que preguntan más de una vez cada 2 segundos o en ráfagas de más de 16 paquetes. La opción kod, disponible desde 4.6, añade respuestas Kiss-o’-Death RATE, que le piden al cliente que baje su frecuencia de sondeo, aunque hay que tener en cuenta que pocas implementaciones lo soportan.

Un punto que se confunde mucho: la notorious amplificación por NTP con el comando monlist (CVE-2013-5211) era un problema de ntpd, no de chrony. chrony nunca responde a las consultas de control heredadas de los modos 6 y 7, así que ese vector de amplificación no existe en chrony. Lo que sí necesitas es el ratelimit para protegerte del abuso de clientes legítimos mal configurados.

Si además quieres que los clientes de tu LAN te hablen con NTS, el servidor necesita su propio certificado y clave para NTS-KE, que es lo que hacen ntsservercert y ntsserverkey. Ojo con un detalle de la documentación: los archivos se cargan una sola vez, así que chronyd necesita reiniciarse para tomar un certificado renovado, y para eso se recomiendan las directivas ntsdumpdir y dumpdir con la opción -r de chronyd.

Cuándo no conviene NTS

No todo escenario necesita NTS, y escribir una configuración que no sirve es tiempo perdido.

No uses NTS si tu fuente es un servidor NTP local de tu propia LAN sin exposición a internet. El tráfico nunca sale de tu red, no hay atacante en ruta que pueda inyectar respuestas, y NTS agrega un handshake TLS por fuente más la complejidad de gestionar los certificados. En ese caso, minsources 3 y maxchange te dan el 80 % del beneficio por una fracción del costo.

No uses NTS con pool.ntp.org. La FAQ de chrony advierte explícitamente sobre systemd-timesyncd con este pool porque los servidores individuales son operados por voluntarios y pueden desviarse o dar un salto hacia el pasado o el futuro. La misma lógica aplica con chrony: un pool con muchos servidores no es un pool con servidores autenticados.

Considera NTS si tu servidor tiene una IP pública y pide la hora a fuentes externas. Ahí el atacante en ruta es una amenaza real, y NTS más los límites de deriva son la diferencia entre un incidente y un log molesto.

Un matiz sobre la fuente: no uses peer para tus fuentes. El manual es claro en que el modo simétrico es menos seguro que el modo cliente/servidor: sin autenticar es vulnerable a denegaciones de servicio fuera de ruta, y aun autenticado sigue siendo susceptible a ataques de replay.

Problemas frecuentes y cómo diagnosticarlos

El reloj se sincroniza pero chronyc -N authdata muestra - en vez de NTS. La causa más común es un firewall que abre 123/UDP pero no 4460/TCP. El NTP normal funciona (no necesita ese puerto) y el NTS-KE no. Revisa el log del sistema:

sudo journalctl -u chrony -n 50 | grep -i nts

Confirma también que el servidor remoto acepta conexiones NTS-KE en ese puerto. Si el servidor escucha NTS-KE en un puerto distinto del 4460 por defecto, se ajusta con la opción ntsport de la directiva server en el cliente, por ejemplo server ntp1.example.net iburst nts ntsport 4460.

NTS falla en una máquina con el reloj muy desviado. Este es el problema del huevo y la gallina: el certificado del servidor NTS-KE tiene fecha de activación y expiración, y si tu reloj está desviado por meses la validación falla antes de que puedas corregirlo. Las soluciones son corregir la fecha a mano, usar el servidor de bootstrap del proveedor, o, como último recurso, nocerttimecheck 1, que desactiva la comprobación temporal de certificados durante el número de actualizaciones que indiques. El manual califica esa última de “importantes implicaciones de seguridad” y recomienda usarla solo como último recurso.

Las cookies NTS no sobreviven a un reinicio. Es el comportamiento por defecto: sin ntsdumpdir, chrony no guarda las cookies. Añade la directiva y verifica que el directorio existe.

El reloj no se actualiza y Leap status dice Not synchronised. Si configuraste minsources 3 y solo tienes una fuente alcanzable, el reloj se queda quieto a propósito. Es el comportamiento correcto, no un error. Revisa cuántos servidores están en línea con:

sudo chronyc activity

Preguntas frecuentes

¿Cómo verifico que NTS está autenticando mis fuentes de verdad?

Corre chronyc -N authdata y revisa la columna Mode: debe decir NTS en cada fuente. Las columnas KeyID, Type y KLen deben tener valores distintos de cero; si KeyID es 0, el establecimiento de claves falló. El Type indica el algoritmo AEAD (30 es AES-128-GCM-SIV, 15 es AES-SIV-CMAC-256).

¿Qué hago si NTS no funciona pero el reloj sí se sincroniza?

Casi siempre es el puerto 4460/TCP bloqueado: el NTP normal corre sobre 123/UDP y funciona, pero el handshake NTS-KE necesita 4460/TCP abierto hacia el servidor. Ábrelo en el firewall del cliente y del servidor, y confirma que el servidor remoto tenga NTS-KE habilitado. Revisa el log del cliente con journalctl -u chrony buscando errores de conexión TLS.

¿Cuál es la diferencia entre maxchange y maxdrift?

Ambos limitan el daño de un ataque, pero en ejes distintos. maxchange acota el desfase de tiempo absoluto aceptado en una actualización: si se supera, chronyd ignora el ajuste y, si vuelve a ocurrir, sale con error. maxdrift acota la deriva de frecuencia en ppm, es decir, cuánto puede correr el reloj respecto al tiempo real entre correcciones.

¿Por qué chronyd se detiene cuando se dispara maxchange?

Es el diseño, no un fallo. Cuando el límite de maxchange se supera, chronyd deja de aceptar ajustes grandes y termina con error para no seguir un reloj manipulado. El manual es explícito: hay que reiniciarlo a mano y no debe ser el gestor de servicios quien lo levante solo, porque un reinicio automático le da al atacante otra oportunidad.

¿Puedo usar chrony en un contenedor o detrás de un NAT?

En un contenedor el ajuste del reloj suele ser imposible por falta de privilegios, y chrony puede quedarse esperando sincronización sin capacidad de establecerla. Ubuntu lo resuelve con chronyd -x cuando no puede fijar la hora, de modo que el contenedor sigue sirviendo NTP sin intentar sincronizar. Para el NAT no hace falta nada especial: NTS negocia las claves por TLS con el servidor, no con el cliente final.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *