Desincronización del reloj y fallos a través de proxy: TLS, JWT, firmas de solicitudes y diagnóstico en un minuto
Contenido del artículo
- Introducción: «ayer funcionaba, hoy el certificado no es válido»
- Fundamentos: dónde es crítica la hora y por qué los protocolos miran el reloj
- Inmersión profunda: de quién es el reloj que importa en la cadena cliente — proxy — servidor
- Cómo se ve el error en distintos clientes: textos que conviene reconocer desde la primera línea
- Por qué se desvían los relojes: virtualización, contenedores, ausencia de demonio, ajuste manual
- Diagnóstico en un minuto: comparación con una fuente externa y dispersión admisible
- Configuración de la sincronización: systemd-timesyncd y chrony, y cómo asegurarse de que realmente funciona
- Contenedores y relojes: qué se hereda del host y qué no
- Errores típicos y casos de la práctica
- Herramientas y checklist «antes de culpar al proxy»
- Faq: preguntas frecuentes sobre hora, proxy y fallos
- Conclusión: la hora como primer punto del diagnóstico
Introducción: «ayer funcionaba, hoy el certificado no es válido»
Hay una clase de incidentes que sabe camuflarse mejor que cualquier otro. Un script que durante meses pasó a través de un proxy hacia decenas de API de repente empieza a fallar en cada solicitud. En el log aparece certificate has expired o certificate is not yet valid. El ingeniero abre el certificado del sitio de destino en el navegador de su portátil: la fecha de vencimiento está en orden, la cadena de confianza está en verde. Reinicia el script sin proxy desde su portátil: funciona. Vuelve a poner el proxy: otra vez falla. La conclusión se impone sola: el culpable es el proxy.
Y esa conclusión casi siempre es errónea. La cosa no está en que el proxy «sustituya el certificado» o «rompa el TLS». La cosa está en que el script se ejecuta en otra máquina —en un servidor, en una máquina virtual, en un contenedor— y en esa máquina el reloj del sistema se desvió. Por unos minutos, unas horas o unos días. El proxy simplemente resultó ser la variable más visible de la ecuación y recibió el golpe.
Vamos a desglosar este tema por completo. Aprenderá en qué lugares exactos los protocolos modernos dependen de la hora actual y por qué un desfase del reloj en distintas direcciones produce síntomas distintos. Verá los mensajes de error concretos en curl, Python y Node.js para reconocer el problema desde la primera línea del trace. Entenderá por qué los relojes se desvían precisamente en máquinas virtuales y contenedores y por qué tener instalado un demonio de sincronización no garantiza que funcione. Obtendrá un procedimiento de diagnóstico de un minuto, configuraciones funcionales de chrony y systemd-timesyncd, y un checklist que conviene repasar antes de escribir al soporte del proveedor de proxy.
Aclaremos los límites de inmediato. Este artículo no trata de la emisión de certificados, ni de cadenas de confianza, ni de almacenes de raíces. Consideramos una única causa de fallos —la hora incorrecta del sistema— y todo lo que de ella se deriva al trabajar a través de un proxy. Es un tema acotado, pero es justamente el que quema horas de depuración en equipos que se dedican a integraciones, monitoreo, recolección de datos abiertos y automatización a través de infraestructura de proxy como Proxeon.
Fundamentos: dónde es crítica la hora y por qué los protocolos miran el reloj
Empecemos con una pregunta simple. ¿Para qué necesitan los protocolos de red saber qué hora es? La respuesta es una: para limitar el tiempo de vida de la confianza. Cualquier secreto —un certificado, un token, una firma, un código de un solo uso— tarde o temprano puede filtrarse. Si tiene fecha de vencimiento, el daño de una filtración queda acotado. Pero la limitación de tiempo funciona solo cuando la parte que verifica conoce la hora actual. Y la toma de un único lugar: del reloj del sistema de la máquina donde se ejecuta la verificación.
De ahí una consecuencia fundamental: cada verificación de tiempo es local. El servidor no le pregunta al cliente qué hora es. El cliente no le pregunta al servidor. Cada uno mira su propio reloj y saca una conclusión. Si los relojes de ambas partes difieren, ven realidades distintas, y una de ellas considera a la otra infractora.
Verificación de la vigencia del certificado en TLS
Cada certificado X.509 contiene dos campos: notBefore (válido desde) y notAfter (válido hasta). Durante el handshake TLS, el cliente compara la hora actual de su reloj con ese intervalo. El momento en que el servidor emitió el certificado no influye aquí: lo único que importa es lo que el cliente piensa sobre la hora actual.
La dirección del desfase determina el síntoma. Si el reloj del cliente atrasa, los certificados recién emitidos parecen como si aún no se hubieran emitido: error «certificate is not yet valid». Si el reloj del cliente adelanta, los certificados parecen vencidos: «certificate has expired». Esta es la primera señal de diagnóstico que conviene memorizar.
¿Por qué el tema se agudizó justo ahora? Porque los certificados son cada vez más efímeros. Muchas autoridades de certificación públicas emiten certificados con 90 días de vigencia, han aparecido variantes de 6 días, y los requisitos del sector reducen sistemáticamente el plazo máximo: desde marzo de 2026, 200 días; luego 100 días; y para 2029, 47 días. Antes, un reloj atrasado dos semanas casi nunca coincidía con un certificado reciente. Ahora el sitio de destino puede reemitir su certificado cada pocos días, y una máquina atrasada tres días lo rechazará como «aún no válido».
Al mismo grupo pertenecen mecanismos asociados: las respuestas OCSP y las listas de revocación tienen sus propias ventanas de vigencia, las marcas de Certificate Transparency contienen sellos de tiempo, y los tickets de sesión de TLS 1.3 viven un tiempo limitado. Todos se verifican con el reloj local.
JWT: campos exp, nbf e iat
JSON Web Token es el formato de token de acceso más extendido en las API. En su cuerpo hay tres campos temporales en formato Unix epoch: iat (cuándo se emitió), nbf (not before — a partir de qué momento es válido) y exp (cuándo expira). La parte que recibe el token los compara con su reloj, normalmente con un pequeño margen (leeway) de 30-60 segundos.
Aquí surge una asimetría interesante. Normalmente el token lo emite el servidor según su reloj, y el cliente lo almacena en caché hasta que llega el exp —pero según su propio reloj. Si el cliente atrasa cinco minutos, seguirá considerando vivo el token cinco minutos después de que el servidor haya empezado a rechazarlo. Obtendrá 401 inexplicables al final de la vida de cada token. Y si el cliente firma él mismo el JWT —por ejemplo, para autenticar una cuenta de servicio— y su reloj adelanta, el servidor ve el iat en el futuro y rechaza el token como aún no válido.
Firmas de solicitudes con marca de tiempo
Muchas API exigen firmar la solicitud con HMAC, incluyendo la hora actual en la cadena firmada. El ejemplo más conocido es el esquema Signature Version 4, que usan los almacenes de objetos compatibles con S3. El servidor acepta la solicitud si su marca de tiempo difiere del reloj del servidor en no más de 15 minutos; de lo contrario devuelve el error RequestTimeTooSkewed. Pasarelas de pago, API bursátiles y de intermediación, y buses de datos corporativos usan esquemas similares con ventanas de entre 30 segundos y varios minutos. La autenticación Kerberos en redes corporativas tolera por defecto una discrepancia de 5 minutos.
Fíjese: la firma la genera su cliente según su reloj. El proxy no participa en ese proceso —físicamente no puede—, solo transmite bytes. Por eso el error «marca de tiempo inválida en la firma» a través de un proxy siempre es una cuestión del reloj de su máquina.
Códigos de un solo uso TOTP
Los códigos de autenticación de dos factores según el estándar TOTP se calculan a partir de un secreto compartido y el número de intervalo de 30 segundos. La parte que verifica normalmente admite más o menos un intervalo. Si su reloj se desvió un minuto, los códigos dejan de aceptarse por completo. Esto también aplica a integraciones propias, donde una cuenta de servicio genera TOTP por software para entrar en un portal corporativo.
Consumidores de tiempo menos evidentes
- Caché HTTP. Los encabezados Date, Expires y max-age se comparan con el reloj local; con un desfase, la caché o no funciona, o entrega datos obsoletos.
- Cookies con Expires. El cliente con reloj atrasado las guarda más tiempo; con reloj adelantado, las descarta de inmediato.
- HSTS y pinning. Las directivas max-age también se calculan localmente.
- Rotación de certificados dentro de clústeres. Los componentes de los orquestadores reemiten certificados según un calendario y, con un desfase del reloj en un nodo, dejan de confiar unos en otros.
Inmersión profunda: de quién es el reloj que importa en la cadena cliente — proxy — servidor
Ahora superpongamos esta imagen a la topología real. Al trabajar a través de un proxy, en la cadena hay al menos tres máquinas con tres conjuntos de relojes: su cliente, el servidor proxy y el servidor de destino. La pregunta clave del diagnóstico es: ¿en cuál de ellos ocurre la verificación que falla?
HTTP CONNECT y SOCKS5: el proxy no ve el TLS
Al solicitar un recurso HTTPS a través de un proxy HTTP, el cliente envía el comando CONNECT con el nombre del host y el puerto. El proxy establece una conexión TCP con el destino y luego reenvía bytes ciegamente en ambas direcciones. El handshake TLS ocurre a través del proxy, entre su cliente y el servidor de destino. El proxy no ve el certificado, no puede modificarlo y no participa en la verificación. Lo mismo vale para SOCKS5.
Conclusión práctica: el error «certificate is not yet valid» o «certificate has expired» al trabajar a través de un túnel transparente no puede físicamente ser causado por el proxy. Su origen está en el reloj de su máquina, en el almacén de raíces de confianza de esa máquina, o en un certificado realmente vencido en el destino. Los dos primeros casos son locales. Y el primero se comprueba en diez segundos.
Cuando se verifican más relojes: TLS hasta el propio proxy
Si se conecta al proxy por un canal seguro (esquema https:// para el propio proxy), el cliente verifica dos certificados: el del servidor proxy y el del sitio de destino. Ambas verificaciones se hacen con el reloj del cliente. La sintomatología es la misma, pero en la salida detallada de curl se verá en qué etapa —«Proxy certificate» o «Server certificate»— ocurrió el fallo.
Qué depende del reloj del propio proxy
El reloj del servidor proxy influye en un conjunto limitado de cosas: la corrección de sus propios logs, el funcionamiento de su listener TLS (por ejemplo, la obtención de respuestas OCSP), y también la verificación de credenciales de autorización si el proveedor usa tokens con vigencia limitada. La infraestructura de Proxeon sincroniza la hora en sus nodos de forma estándar, y en la inmensa mayoría de los incidentes el reloj del proxy no tiene nada que ver: no participa ni en la verificación del certificado de destino, ni en la formación de sus firmas, ni en la validación de sus JWT. Todo eso lo hacen el cliente y el servidor de destino.
Reloj del servidor de destino
En raras ocasiones el reloj se desvía en el otro lado. Señal: los errores ocurren solo con un host, mientras que el acceso a los demás es normal; el encabezado Date en la respuesta de ese host difiere notablemente del Date de otros servidores. No puede hacer nada al respecto salvo informar al propietario y, si la API lo permite, compensar el desfase en sus solicitudes —algunas bibliotecas saben calcular la corrección a partir del encabezado Date de la respuesta con error y firmar la siguiente solicitud teniendo en cuenta el desfase.
Latencia y «expiración en cola»
El proxy añade retardo —desde decenas de milisegundos hasta segundos al reconectar canales móviles y rotar IP. Para una ventana de 15 minutos es insignificante. Pero hay una trampa sutil en la lógica de reintentos. Si firmó la solicitud, la puso en cola y la envió varios minutos después de una serie de reintentos con espera, la marca de tiempo de la firma se queda obsoleta. A través de un canal lento o sobrecargado, esto ocurre con más frecuencia. Regla: firme justo antes de enviar y vuelva a firmar en cada reintento, en lugar de reutilizar un objeto ya firmado.
Desfase: magnitud y dirección
Reunamos la dependencia del síntoma respecto al desfase en una lista que será útil al leer logs:
- El reloj del cliente atrasa: «certificate is not yet valid», JWT con error nbf o iat al autofirmarse, sus tokens en caché parecen vivos pero el servidor responde 401 «expired», las firmas se rechazan como demasiado antiguas, el TOTP no se acepta.
- El reloj del cliente adelanta: «certificate has expired», sus propios JWT se rechazan con iat en el futuro, las firmas se rechazan como «del futuro», los tokens se descartan de la caché antes de tiempo, el TOTP no se acepta.
- Desfase menor a un segundo: ninguno de los mecanismos mencionados lo notará.
- Desfase de 30 segundos a 5 minutos: se rompen TOTP, JWT con leeway pequeño, esquemas HMAC estrictos.
- Desfase mayor a 15 minutos: se rompen prácticamente todas las firmas de solicitudes y Kerberos.
- Desfase en días: empiezan a fallar las conexiones TLS a sitios con certificados efímeros.
Cómo se ve el error en distintos clientes: textos que conviene reconocer desde la primera línea
Saber reconocer el problema por el texto del error ahorra más tiempo que cualquier metodología. Abajo están las formulaciones características de clientes populares. Se muestran tal como las imprimen las bibliotecas con OpenSSL por debajo; los números de línea entre paréntesis variarán de una versión a otra.
curl
Mensajes básicos con hora incorrecta:
curl: (60) SSL certificate problem: certificate is not yet validcurl: (60) SSL certificate problem: certificate has expiredEl código 60 es CURLE_PEER_FAILED_VERIFICATION, común a todos los problemas de verificación de certificado. Distinguir la causa temporal de un problema con las raíces solo es posible por el texto después de los dos puntos. Con el flag -v aparece una línea útil con el código numérico de la verificación de OpenSSL: 9 corresponde a «not yet valid», 10 a «has expired»:
* SSL certificate verify result: certificate is not yet valid (9), continuing anyway.La línea «continuing anyway» aparece solo con el flag -k; sin él, la conexión se rompe. En Windows con el backend Schannel el texto es distinto:
curl: (60) schannel: SEC_E_CERT_EXPIRED (0x80090328) - The received certificate has expired.Ejemplo de verificación completa a través de proxy con salida detallada, para ver tanto la etapa (proxy o destino) como el resultado de la verificación:
curl -v --proxy http://LOGIN:PASSWORD@PROXY_HOST:PORT https://example.com/ 2>&1 | grep -iE 'verify|expire|valid|date'Python: ssl, requests, httpx, aiohttp
El módulo estándar ssl lanza una excepción con un sufijo elocuente:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate is not yet valid (_ssl.c:1010)La biblioteca requests la envuelve en SSLError y añade el envoltorio del pool de conexiones, por lo que las palabras clave quedan al final de una cadena larga:
requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1010)')))Detalle importante al trabajar a través de proxy: con un túnel CONNECT, el error de verificación del certificado de destino llega precisamente como SSLError, no como ProxyError. ProxyError aparece si no se pudo establecer la conexión con el propio proxy. Así que el tipo de excepción ya indica dónde buscar.
httpx lo formula más breve:
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1010)aiohttp —con su propia clase:
aiohttp.client_exceptions.ClientConnectorCertificateError: Cannot connect to host example.com:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate is not yet valid (_ssl.c:1010)')]Errores JWT en PyJWT al verificar el token de su lado:
jwt.exceptions.ExpiredSignatureError: Signature has expiredjwt.exceptions.ImmatureSignatureError: The token is not yet valid (nbf)Y aquí un ejemplo mínimo reproducible a través de proxy que conviene tener a mano como caso de prueba:
import requests, ssl, sys
PROXY = "http://LOGIN:PASSWORD@PROXY_HOST:PORT"
try:
r = requests.get("https://example.com/", proxies={"https": PROXY}, timeout=15)
print(r.status_code, r.headers.get("Date"))
except requests.exceptions.SSLError as e:
msg = str(e)
if "not yet valid" in msg or "has expired" in msg:
print("Parece un desfase del reloj del sistema, verifique date -u")
print(msg)Una breve aclaración: el símbolo de salto de línea en este y los siguientes ejemplos se muestra como entidad HTML para que el marcado quede en una sola línea; al copiarlo al editor se convertirá en un salto de línea normal.
Node.js: https, fetch, undici
Node imprime mensajes cortos y, lo que es valioso, dota al error de un campo code:
Error: certificate is not yet valid
code: 'CERT_NOT_YET_VALID'Error: certificate has expired
code: 'CERT_HAS_EXPIRED'Trampa del fetch integrado: por fuera solo se ve TypeError: fetch failed, y la causa real está escondida en la propiedad cause. Regístrela explícitamente:
try {
const res = await fetch("https://example.com/");
console.log(res.status, res.headers.get("date"));
} catch (e) {
console.error(e.message, e.cause && e.cause.code, e.cause && e.cause.message);
}Para enrutar fetch a través de un proxy en Node se usa ProxyAgent del paquete undici. El error del certificado de destino llegará con el mismo código CERT_HAS_EXPIRED o CERT_NOT_YET_VALID en cause. Para el módulo clásico https y bibliotecas como axios se emplea un agente del paquete https-proxy-agent —los códigos de error son los mismos, ya que los genera el mismo OpenSSL dentro de Node.
La biblioteca jsonwebtoken, al verificar tokens, distingue dos casos temporales:
TokenExpiredError: jwt expired
NotBeforeError: jwt not activeErrores del lado del servidor en firmas
Aquí el texto lo define la API, pero las formulaciones típicas son reconocibles. En almacenes compatibles con S3:
<Code>RequestTimeTooSkewed</Code>
<Message>The difference between the request time and the current time is too large.</Message>En servidores OAuth —el encabezado WWW-Authenticate con error="invalid_token" y descripciones como «The token expired» o «Token used before issued». En API financieras —mensajes sobre timestamp outside of recvWindow o «Timestamp for this request is outside of the allowed window». Si ve cualquiera de estas frases, el reloj es lo primero que hay que revisar.
Por qué se desvían los relojes: virtualización, contenedores, ausencia de demonio, ajuste manual
El oscilador de cuarzo de la placa base no es un patrón. Su típica desviación es de 10-100 ppm, es decir, hasta 1-9 segundos de deriva al día sin corrección. En un mes sin sincronización se acumulan minutos. Es el fondo que tiene cualquier máquina. Pero los incidentes principales no se deben a una deriva lenta, sino a saltos. Veamos las fuentes.
Máquinas virtuales: pausa, snapshot, migración
Una máquina virtual no posee el reloj físico. Recibe la hora a través de una fuente paravirtualizada (kvm-clock, Hyper-V reference time, análogos en otros hipervisores). Cuando el invitado se pausa, se guarda en un snapshot o migra entre hosts, su reloj interno «se congela». Tras reanudarse, el kernel o bien recibe una corrección del hipervisor, o no —depende de la configuración de los agentes invitados. Escenario clásico: el equipo revirtió un servidor de pruebas a un snapshot de hace dos semanas, y este despertó con un reloj atrasado 14 días. Todos los certificados emitidos en esas dos semanas son para él «aún no válidos».
Un problema aparte es el conflicto de dos fuentes de verdad. El agente invitado del hipervisor «ajusta» periódicamente la hora al host, mientras que dentro del invitado corre su propio demonio NTP. Si el host mismo no está sincronizado, el invitado recibe dos señales contradictorias y el reloj puede saltar adelante y atrás. La recomendación de la mayoría de las guías de virtualización: o sincronización desde el hipervisor, o NTP dentro, pero no ambos a la vez.
Portátiles de desarrolladores: suspensión y máquinas virtuales de escritorio
Docker Desktop y WSL2 funcionan dentro de una máquina virtual ligera. Tras la suspensión o hibernación del portátil, el reloj de esa VM no rara vez atrasa exactamente el tiempo de suspensión —una hora, una noche, un fin de semana. El sistema operativo anfitrión sí ajusta la hora, pero la VM no siempre. Resultado: el lunes por la mañana todos los scripts en contenedores fallan en TLS, mientras que en la terminal del host todo está bien. Es justamente esta situación la que suele generar la queja «funciona sin proxy desde el portátil, no funciona a través de proxy en el contenedor» —aunque el proxy en ninguno de los dos casos participara siquiera en la verificación.
Contenedores
Un contenedor no tiene reloj de tiempo real propio —usa el reloj del host. Lo veremos en detalle en una sección aparte; aquí fijemos lo principal: si el host se desvió, se desviaron todos los contenedores, y arreglarlo desde dentro del contenedor es imposible.
Ausencia de demonio de sincronización o su inoperancia
Las imágenes mínimas, las instalaciones «peladas» y los sistemas autocompilados a menudo vienen sin cliente NTP. Y donde lo hay, puede no funcionar por decenas de razones: el UDP saliente al puerto 123 está bloqueado por el firewall; no se resuelven los nombres de los pools; el servicio está instalado pero enmascarado; chrony y timesyncd están instalados simultáneamente y se estorban; el desfase al arrancar superó el umbral en el que el ntpd clásico se niega a corregir y termina. Son especialmente vulnerables los servidores cuyo único acceso a Internet pasa por un proxy HTTP corporativo: NTP por UDP a través de tal proxy no pasará, y el reloj queda librado a sí mismo.
Ausencia de RTC y batería agotada
Los ordenadores de placa única como Raspberry Pi y muchos mini-PC no tienen reloj de hardware con pila. Al arrancar parten de la época o de la fecha del último guardado, y viven así hasta la primera sincronización exitosa. En servidores antiguos se agota la pila CMOS y, tras cada apagado, la BIOS restablece la fecha a la de fábrica. Si la sincronización no está disponible, la máquina funciona con una fecha de hace diez años, y cualquier conexión TLS está condenada.
Ajuste manual y confusión con husos horarios
El comando date -s, ejecutado «para probar la hipótesis» y olvidado, es un argumento frecuente. Otro es el arranque dual Windows y Linux en la misma máquina: Windows guarda en el reloj de hardware la hora local, Linux por defecto espera UTC, y tras cada reinicio en el otro sistema la hora se desplaza en la magnitud del huso horario, para la hora de Moscú —tres horas. Y, por último, la confusión entre hora y huso horario: cambiar la variable TZ o el archivo /etc/localtime cambia la visualización, pero no la hora del sistema. Hay que comparar solo en UTC.
Diagnóstico en un minuto: comparación con una fuente externa y dispersión admisible
El objetivo de este procedimiento es responder en 60 segundos a una pregunta: ¿va bien el reloj de la máquina desde la que se ejecutan las solicitudes a través del proxy. Todos los comandos se ejecutan en esa misma máquina o en ese mismo contenedor donde falla el cliente, no en su portátil.
Paso 1. Vea la hora UTC y compárela con cualquier fuente confiable
date -uCompare con el reloj del teléfono, sincronizado con el operador de telefonía. Una discrepancia de minutos se ve de inmediato. Para los segundos se necesitan los pasos siguientes.
Paso 2. Obtenga la hora del encabezado HTTP Date a través del mismo proxy
Cualquier servidor HTTP correcto entrega el encabezado Date según su propio reloj (normalmente sincronizado). Truco: solicite por HTTP normal, no HTTPS, porque con un desfase fuerte el TLS no se establecerá y no obtendrá el encabezado en absoluto:
date -u; curl -sI --proxy http://LOGIN:PASSWORD@PROXY_HOST:PORT http://example.com/ | grep -i '^date'Las dos líneas deben coincidir con precisión de uno o dos segundos (el encabezado tiene granularidad de segundos, más el retardo del canal). Si nota un minuto o más, el diagnóstico está listo. Este truco es especialmente valioso porque no requiere UDP, funciona a través de cualquier proxy y no depende de TLS.
El mismo truco en Python —cómodo para incorporarlo al healthcheck de un servicio que sale a través de Proxeon o de cualquier otro proxy:
import email.utils, time, requests
PROXY = "http://LOGIN:PASSWORD@PROXY_HOST:PORT"
r = requests.head("http://example.com/", proxies={"http": PROXY}, timeout=10)
server_ts = email.utils.parsedate_to_datetime(r.headers["Date"]).timestamp()
skew = time.time() - server_ts
print(f"Desfase del reloj local: {skew:+.1f} s")
if abs(skew) > 30:
raise SystemExit("El reloj se desvió, la sincronización es obligatoria")Paso 3. Pregúntele al sistema si se considera sincronizado
timedatectlBusque dos líneas: «System clock synchronized: yes» y «NTP service: active». Cualquier «no» o «inactive» es motivo para ir a la sección de configuración. Si tiene chrony:
chronyc trackingMire «System time» (magnitud y signo del desfase), «Stratum» (no debe ser 0) y «Leap status» (debe ser Normal, no «Not synchronised»).
Paso 4. Consulta puntual a un servidor NTP sin cambiar el reloj
Si el UDP saliente está abierto, se puede medir el desfase con precisión sin cambiar nada:
chronyd -Q 'pool pool.ntp.org iburst'O, si tiene ntpdate instalado:
ntpdate -q pool.ntp.orgLa salida mostrará el offset en segundos con signo.
Paso 5. Confirme que es precisamente la hora lo que rompe el TLS
La utilidad openssl sabe ir a través de un proxy HTTP e imprime el resultado de la verificación con código numérico:
openssl s_client -connect example.com:443 -proxy PROXY_HOST:PORT -servername example.com </dev/null 2>/dev/null | grep 'Verify return code'«Verify return code: 9 (certificate is not yet valid)» o «10 (certificate has expired)» con un certificado vivo es una prueba concluyente. Tenga en cuenta que s_client en la mayoría de las compilaciones no admite autenticación en el proxy con usuario y contraseña; para esta prueba conviene usar la autenticación por IP, que sí admite Proxeon. Las fechas del propio certificado las extrae así:
openssl s_client -connect example.com:443 -proxy PROXY_HOST:PORT -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -datesQué dispersión considerar normal
- Hasta 100 milisegundos — máquina sana con NTP funcionando. No hay que hacer nada.
- 100 ms — 1 segundo — aceptable para todos los protocolos analizados, pero indica una fuente pobre o un sondeo poco frecuente. Conviene revisar la configuración.
- 1 — 30 segundos — zona amarilla. TLS y firmas con ventana amplia aún funcionan; TOTP y JWT con leeway pequeño ya pueden fallar. Corregir hoy.
- Más de 30 segundos — zona roja. Considere que cualquier error de autenticación o TLS se debe al reloj, salvo prueba en contrario.
Cómo reproducir el problema a propósito
Para confirmar que su manejo de errores reconoce correctamente un fallo temporal, no hace falta tocar el reloj del sistema. La biblioteca libfaketime sustituye la hora para un solo proceso:
faketime '2024-01-01 12:00:00' curl -sS https://example.com/ -o /dev/nullObtendrá «certificate is not yet valid» para cualquier sitio con un certificado emitido después de esa fecha. Para verificar el certificado sin red, OpenSSL admite el flag -attime con una hora Unix, cómodo para pruebas en CI.
Configuración de la sincronización: systemd-timesyncd y chrony, y cómo asegurarse de que realmente funciona
Instalar el paquete no es sincronizar. Abajo hay dos esquemas funcionales y, lo que es más importante, la metodología de verificación.
Elección: timesyncd o chrony
systemd-timesyncd — cliente SNTP minimalista incluido en systemd. Sondea un solo servidor de la lista, sabe corregir el reloj por salto y suavemente, no funciona como servidor, no admite fuentes de hardware. Suficiente para estaciones de trabajo y servidores no críticos donde se necesita precisión de decenas de milisegundos.
chrony — implementación completa de NTP. Combina varias fuentes, converge rápido tras largos periodos de inactividad, se comporta con cuidado con redes intermitentes, admite NTS (NTP con autenticación mediante TLS) y puede repartir la hora dentro de la red. Es la elección por defecto para servidores, máquinas virtuales y cualquier nodo por el que pasen solicitudes firmadas y trabajo con proxy.
Importante: dos demonios a la vez son una fuente de caos. Al instalar chrony, las distribuciones suelen desactivar timesyncd, pero conviene verificarlo explícitamente.
Variante A: systemd-timesyncd
Configuración en el archivo /etc/systemd/timesyncd.conf:
[Time]
NTP=ntp.your-provider.internal 0.pool.ntp.org 1.pool.ntp.org
FallbackNTP=2.pool.ntp.org 3.pool.ntp.org
PollIntervalMinSec=32
PollIntervalMaxSec=2048Habilitación y verificación:
sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd
timedatectl timesync-statusEn la salida de timesync-status el campo Server debe estar relleno, el Stratum distinto de 0, y el campo Offset en milisegundos. Si el servidor no está indicado o pone «Packet count: 0», no llegan respuestas NTP —casi con seguridad el UDP 123 está cerrado.
Variante B: chrony
Archivo /etc/chrony/chrony.conf (en algunos sistemas /etc/chrony.conf):
server ntp.your-provider.internal iburst prefer
pool pool.ntp.org iburst maxsources 4
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chronyAnalicemos las directivas clave, porque son justamente las que determinan el comportamiento ante fallos:
- iburst — al arrancar envía una serie de solicitudes seguidas y se sincroniza en segundos, no en minutos.
- makestep 1.0 3 — si el desfase supera 1 segundo, en las primeras tres actualizaciones se permite un salto; después, solo corrección suave. Sin esta directiva, tras un snapshot o suspensión, chrony «alcanzará» el reloj durante semanas. Para servidores que se suspenden o migran con frecuencia, algunos ponen makestep 1.0 -1, permitiendo el salto siempre.
- rtcsync — escribe periódicamente la hora en el reloj de hardware, para que tras el reinicio se arranque desde un valor cercano.
- Servidor interno del proveedor primero — las plataformas en la nube suelen proporcionar una dirección NTP dentro de la red; está disponible incluso con el UDP externo cerrado y tiene la latencia mínima. Tome la dirección de la documentación de su proveedor.
Arranque:
sudo systemctl enable --now chrony
sudo chronyc makestep
chronyc tracking
chronyc sources -vCómo entender que la sincronización realmente funciona
Esta es la parte principal de la sección. Señales de una sincronización funcional en chrony:
- En chronyc tracking el campo Reference ID no es cero y el Stratum está en el rango 2-4 para un servidor típico. Stratum 0 o 10 con Reference ID vacío significa «trabajo conmigo mismo».
- Leap status: Normal. El valor «Not synchronised» significa que nadie corrige el reloj.
- System time muestra el desfase en milisegundos o microsegundos, no en segundos.
- En chronyc sources al menos una fuente está marcada con asterisco (la actualmente elegida) o con signo más (participa en la combinación). Un signo de interrogación en todas las fuentes —las respuestas no llegan, busque el firewall o el DNS.
- Last offset y RMS offset disminuyen con el tiempo, no crecen.
Para timesyncd la verificación equivalente es «System clock synchronized: yes» en timedatectl y un Packet count distinto de cero en timesync-status.
Cuando el NTP está bloqueado por el firewall
Si en el servidor solo está permitido el HTTP(S) saliente a través de un proxy, tiene tres caminos. El primero — pedir que abran el UDP 123 al menos hacia el servidor NTP interno; es una petición estándar y segura para los administradores de red. El segundo — sincronizarse desde el hipervisor o el host, si es una máquina virtual o un contenedor. El tercero — un compromiso mediante la utilidad htpdate, que ajusta el reloj según el encabezado Date de las respuestas HTTP; la precisión es del orden de un segundo, suficiente para TLS, JWT y firmas de solicitudes, y sabe trabajar a través de un proxy HTTP. No es lo más elegante, pero sí una opción viable para segmentos aislados.
Windows
En hosts Windows el servicio de hora se verifica así:
w32tm /query /status
w32tm /resyncEn un entorno de dominio la fuente es el controlador de dominio; fuera del dominio, un servidor NTP configurado. El campo Last Successful Sync Time debe ser reciente.
Contenedores y relojes: qué se hereda del host y qué no
Un contenedor es un conjunto de espacios de nombres aislados sobre un kernel compartido. El aislamiento afecta a procesos, red, sistema de archivos, puntos de montaje. Pero el reloj de tiempo real (CLOCK_REALTIME) no se aísla. Los espacios de nombres de tiempo, aparecidos en Linux 5.6, permiten desplazar solo los relojes monótonos y el tiempo desde el arranque —esto es necesario para migrar contenedores y no afecta a la hora calendárica. De ahí se derivan varias consecuencias tajantes.
Qué se hereda
- Hora calendárica. Todos los contenedores ven exactamente la misma hora que el host. Si el host se desvió, se desviaron todos.
- Sincronización. Un chrony funcionando en el host «arregla» automáticamente la hora en todos los contenedores.
- Saltos. Si el host corrigió el reloj con un salto, las aplicaciones en los contenedores verán ese salto al instante.
Qué no se hereda
- Huso horario. El archivo /etc/localtime y la variable TZ viven dentro de la imagen. En imágenes mínimas como Alpine el paquete tzdata puede faltar, y el contenedor siempre mostrará UTC. No es un error de hora, sino de visualización, pero confunde regularmente al leer logs.
- Almacén de certificados raíz. No es el tema de este artículo, pero recordemos: también está dentro de la imagen.
- Derecho a cambiar la hora. La llamada al sistema para fijar la hora requiere CAP_SYS_TIME, que el contenedor no tiene por defecto. El comando date -s dentro del contenedor fallará con «Operation not permitted». Ejecutar un demonio NTP dentro de un contenedor normal no tiene sentido, y darle CAP_SYS_TIME es una mala práctica: empezará a gestionar el reloj de todo el host.
Consecuencias prácticas
El diagnóstico de la sección anterior se puede y se debe ejecutar dentro del contenedor — date -u y la solicitud del encabezado Date a través del proxy mostrarán la imagen real. Pero la corrección siempre ocurre en el host. Si el contenedor corre en Kubernetes, el host necesario es el nodo concreto donde está programado el pod; asegúrese de que chrony forme parte de la imagen base de los nodos y de que el monitoreo recoja la métrica de desfase de cada nodo.
Docker Desktop y WSL2 en portátiles son una historia aparte, porque el «host» para los contenedores allí es una máquina virtual oculta propensa a atrasarse tras la suspensión. Para WSL2, una corrección rápida es tomar la hora del reloj de hardware, que el hipervisor mantiene en orden:
sudo hwclock -sPara Docker Desktop, el método radical pero fiable es reiniciar la aplicación; tras el arranque la VM se sincroniza con el host. En versiones recientes el problema es menos frecuente, pero no ha desaparecido del todo, por lo que un healthcheck de desfase del reloj en el entorno de desarrollo no es paranoia, sino ahorro de una hora matutina.
Ejemplo: healthcheck en un archivo compose
La idea es no levantar el contenedor de trabajo si el reloj del host se desvió más de lo admisible. La verificación usa el mismo truco del encabezado Date por HTTP normal a través del proxy, para no depender del TLS:
healthcheck:
test: ["CMD", "python", "/app/clock_check.py"]
interval: 5m
timeout: 20s
retries: 1El script clock_check.py es el que escribimos en la sección de diagnóstico, con umbral de 30 segundos. El orquestador marcará el contenedor como no saludable y verá la causa en el estado, en lugar de una pila de errores TLS en los logs de la aplicación.
Errores típicos y casos de la práctica
La sección sobre lo que no hay que hacer es más útil que la de lo que hay que hacer. Aquí se recopilan soluciones que parecen lógicas en el momento del incidente y salen caras después, además de varias historias características con cifras.
Error 1: desactivar la verificación del certificado
El flag -k, verify=False, NODE_TLS_REJECT_UNAUTHORIZED=0. El script «vuelve a funcionar», la tarea se cierra. Pero las firmas de solicitudes y los JWT siguen sin coincidir, el TOTP no se acepta, y usted ha desactivado el único mecanismo que protegía el canal. El reloj, mientras tanto, sigue desviado y en una semana romperá algo más. Desactivar la verificación es admisible exactamente para un comando como parte del diagnóstico, y nunca en el código.
Error 2: aumentar el leeway o la ventana de firma «con margen»
Subir la tolerancia de verificación del JWT a 10 minutos significa ampliar la ventana para reutilizar un token interceptado 20 veces. El problema real (el reloj) no desaparece. El leeway correcto para un JWT es 30-60 segundos; para firmas HMAC, el que defina el proveedor de la API.
Error 3: ajustar la hora a mano en lugar de sincronizar
date -s corrige el síntoma unas horas. La deriva y el siguiente snapshot devolverán todo, solo que ahora nadie recordará que la hora se ajustó a mano.
Error 4: dos demonios de hora a la vez
chrony y timesyncd, o chrony y el agente invitado del hipervisor con sincronización activada, tiran del reloj en direcciones opuestas. En los logs de chrony esto se ve como correcciones grandes constantes. Deje una sola fuente de verdad.
Error 5: comparar la hora local con UTC
Una diferencia de exactamente 3, 5 o 7 horas casi siempre no es un fallo del reloj, sino husos horarios. Compare siempre date -u con una fuente UTC y en los logs escriba la hora con la zona indicada explícitamente.
Error 6: confiar en el estado «installed»
El rol de Ansible instaló chrony; la tarea está en verde. Pero el UDP 123 está cerrado y chrony está sincronizado «consigo mismo». La verificación en la configuración de la infraestructura debe mirar el Leap status y el desfase, no el hecho de tener el paquete.
Error 7: reutilizar una solicitud firmada en los reintentos
Especialmente a través de un proxy con reconexiones. Cada intento debe recibir una nueva marca de tiempo y una nueva firma.
Caso A: snapshot del entorno de pruebas
El equipo de recolección de datos abiertos de precios mantenía un pool de workers en máquinas virtuales y una vez por semana revertía el entorno a un snapshot «limpio». Tras una de las reversiones, todos los workers que salían a través del proxy empezaron a fallar con «certificate is not yet valid» en un tercio de los sitios de destino —precisamente los que usaban certificados de 90 días y habían alcanzado a reeditarlos durante los 12 días de vida del snapshot. Desde el portátil del ingeniero, sin proxy, los sitios se abrían. Hora y media se fue en cambiar pools y planes del proveedor de proxy antes de que alguien ejecutara date -u y viera un atraso de 12 días. Corrección: chrony con makestep en la imagen base del entorno y verificación del desfase en el script de post-restauración. No hubo repeticiones.
Caso B: segmento aislado y mini-PC
Un mini-PC sin reloj de hardware estaba en un segmento de red donde solo se permite HTTP saliente a través del proxy corporativo. Tras un corte de luz programado, arrancó con la fecha de compilación de la imagen —14 meses en el pasado. Ninguna conexión TLS se establecía, lo que parecía un fallo total del proxy. Solución: se permitió UDP 123 hacia el servidor NTP interno de la empresa, chrony recibió la directiva makestep 1.0 -1, y como previsión ante la falta de NTP se añadió el arranque de htpdate a través del proxy al inicio.
Caso C: exportación a almacenamiento de objetos desde Docker Desktop
Un desarrollador depuraba la exportación de archivos a un almacenamiento compatible con S3 a través de un proxy desde un contenedor en su portátil. Tras la pausa del almuerzo, todas las solicitudes empezaron a devolver RequestTimeTooSkewed. El desfase del reloj de la máquina virtual de Docker Desktop era de 23 minutos, un poco más de la ventana de 15 minutos. Reiniciar Docker Desktop resolvió el asunto en un minuto; se añadió al proyecto un healthcheck de desfase que muestra la causa de inmediato.
Caso D: 50 segundos y TOTP
Un servicio automatizaba el acceso a su propio portal corporativo con una cuenta de servicio y autenticación de dos factores por TOTP. Los códigos dejaron de aceptarse: el servidor respondía que el código era incorrecto. El desfase del reloj del nodo era de 50 segundos, porque timesyncd estaba instalado pero enmascarado al migrar a chrony, que nunca se activó. Con una tolerancia de un solo intervalo de 30 segundos, eso bastó. Cero cambios en el código, solo arrancar chrony.
Herramientas y checklist «antes de culpar al proxy»
Reunamos todo en una forma que se pueda adjuntar a un ticket o incorporar a un runbook. Primero las herramientas, después la secuencia.
Herramientas
- date -u, timedatectl, chronyc, hwclock — estado del reloj y de la sincronización.
- curl -v --proxy, openssl s_client -proxy — verificación de TLS a través del proxy con código de verificación.
- chronyd -Q, ntpdate -q — medición puntual del desfase sin cambiar el reloj.
- Encabezado Date por HTTP — fuente universal de hora, que funciona a través de cualquier proxy.
- libfaketime, openssl -attime — reproducción del fallo y prueba del manejo de errores.
- htpdate — sincronización de emergencia en segmentos donde el UDP está cerrado.
- Métricas — los exportadores de chrony o timesyncd entregan el desfase al sistema de monitoreo; el umbral de alerta es 1 segundo y el crítico, 30 segundos.
Checklist
- ¿Dónde se ejecuta exactamente el código que falla? Portátil, servidor, VM, contenedor, pod. Todas las verificaciones se hacen allí.
- ¿date -u coincide con el teléfono? Una discrepancia de minutos — alto, arregle el reloj.
- ¿El encabezado Date por http:// a través del proxy coincide con el UTC local dentro de un par de segundos? Si no — es el reloj.
- ¿timedatectl dice synchronized: yes y chronyc tracking muestra Leap status Normal? Si no — la sincronización no funciona, independientemente de lo que esté instalado.
- ¿El texto del error contiene not yet valid, has expired, RequestTimeTooSkewed, jwt expired, jwt not active, timestamp? Cada una de estas palabras es sobre la hora.
- ¿Sin proxy desde la misma máquina el error es el mismo? Si sí — el proxy no tiene nada que ver.
- ¿A través del proxy desde otra máquina no hay error? Si sí — el problema está en la primera máquina.
- ¿openssl s_client a través del proxy muestra Verify return code 9 o 10 con las fechas del certificado vivas? Diagnóstico confirmado.
- ¿Los errores son solo con un host de destino? Compare su Date con el de otros — puede que el reloj se haya desviado de su lado.
- ¿Se reforman las firmas en cada reintento? Si no — corrija la lógica de reintentos.
- ¿Hubo recientemente un snapshot, suspensión, migración, reinicio tras un apagado prolongado? Cualquier «sí» es casi con seguridad la causa.
- Solo tras todos los «no» — contacto con el proveedor de proxy adjuntando las salidas de date -u, chronyc tracking y curl -v. Así el soporte de Proxeon o de cualquier otro servicio resolverá la cuestión en una sola ronda de mensajes, y no en cinco.
FAQ: preguntas frecuentes sobre hora, proxy y fallos
¿Puede el proxy mismo distorsionar la hora en mis solicitudes o en el TLS?
Al tunelizar a través de CONNECT o SOCKS5 — no. El handshake TLS ocurre entre su cliente y el servidor de destino; el proxy solo ve bytes cifrados. Las marcas de tiempo de las firmas y de los JWT las forma su código en su máquina. Lo único que el proxy puede añadir es retardo, y se mide en milisegundos, no en minutos.
¿Por qué desde el portátil sin proxy funciona, y desde el servidor a través del proxy no?
Porque son dos máquinas distintas con dos conjuntos de relojes distintos. La comparación es incorrecta por construcción: cambiaron dos variables a la vez. Para aislar la influencia del proxy, ejecute la solicitud desde el servidor sin proxy. Para aislar la influencia de la máquina, ejecútela a través del proxy desde el portátil. Casi siempre resultará que el error está ligado a la máquina.
¿Qué precisión debe tener la hora para trabajar a través de proxy?
Para TLS basta una precisión de horas si los certificados son longevos, y de días para los efímeros. Para firmas de solicitudes la ventana típica es de 5-15 minutos. Para JWT, 30-60 segundos de tolerancia. Para TOTP, más o menos 30 segundos. Un NTP funcionando da milisegundos, por lo que el estándar práctico es un desfase menor a 1 segundo, y alerta al superar los 30 segundos.
¿Puedo ejecutar un cliente NTP dentro del contenedor?
Técnicamente — solo con CAP_SYS_TIME, y entonces gestionará el reloj de todo el host, lo que rompe el aislamiento y entra en conflicto con el demonio del host. El enfoque correcto es sincronizar en el host o en el nodo del clúster. En el contenedor, deje solo la verificación del desfase.
¿Qué hacer si el UDP 123 está totalmente cerrado y la salida solo es a través de un proxy HTTP?
Solicite la apertura del UDP al menos hacia el NTP interno de la empresa o del proveedor de nube —es una necesidad de infraestructura estándar. Mientras se resuelve, use la sincronización desde el hipervisor o htpdate por los encabezados Date a través del proxy. Tenga en cuenta que NTS protege NTP mediante TLS en el puerto TCP 4460, pero los propios paquetes de tiempo siguen yendo por UDP, así que NTS no sortea el bloqueo de UDP.
El reloj adelanta varios minutos, pero TLS funciona. ¿Por qué entonces fallan las solicitudes a la API?
Porque en TLS la ventana es de días, y en las firmas y tokens, de segundos y minutos. Es una situación normal: los certificados se verifican con éxito, pero la firma HMAC con una marca de tiempo adelantada tres minutos es rechazada por el servidor con una ventana estricta. Mire el texto del error de la API, no solo el éxito de la conexión.
¿Cómo entender que se desvió el reloj del servidor de destino y no el mío?
Compare el encabezado Date de la respuesta del host problemático con el Date de dos o tres hosts más y con su date -u. Si su reloj coincide con la mayoría, y un host se desvía —el problema está en su lado. Algunos SDK de cliente saben compensar automáticamente el desfase del servidor, calculándolo a partir de la respuesta con error de tiempo.
¿Hay que volver a firmar la solicitud en cada reintento a través del proxy?
Sí. La firma incluye la marca de tiempo y es válida dentro de la ventana. Si entre el primer intento y el reintento pasaron minutos de espera y reconexiones, la firma antigua puede salir de la ventana. Forme la firma en el momento de enviar cada intento.
¿Cómo incorporar la verificación del reloj al monitoreo?
Tome el desfase de chrony o timesyncd al sistema de métricas y defina dos umbrales: advertencia a 1 segundo, crítico a 30 segundos. Además —un healthcheck del servicio por el encabezado Date a través del proxy de trabajo; captura situaciones en las que el demonio de hora está formalmente vivo pero el reloj igual se desvió, por ejemplo tras restaurar un snapshot.
¿Cambió algo en 2026?
El cambio principal es la reducción de los plazos de vigencia de los certificados públicos y la difusión masiva de la reemisión automática. Esto convierte incluso pequeños atrasos del reloj, de unos días, en causa real de fallos TLS, cuando antes tales fallos eran raros. Al mismo tiempo crece la proporción de JWT cortos y ventanas estrictas de firmas en las API. En otras palabras, los requisitos de precisión del reloj en las máquinas cliente aumentan, y la sincronización de la hora deja de ser «una configuración por defecto en la que nadie piensa».
Conclusión: la hora como primer punto del diagnóstico
Resumamos. La verificación de la vigencia del certificado, de los campos exp y nbf del JWT, de la marca de tiempo en la firma de la solicitud y del código de un solo uso —todo son comparaciones locales con el reloj del sistema. Al trabajar a través de un proxy aparece una máquina más en la cadena, y la atención se desplaza naturalmente hacia ella, aunque ni el handshake TLS, ni la firma, ni la validación del token pasan por el túnel del proxy. El proxy en estos escenarios es solo transporte. Los relojes, en cambio, se desvían por razones bien terrenales: snapshots y pausa de máquinas virtuales, suspensión de portátiles con Docker Desktop y WSL2, ausencia o inoperancia del demonio de sincronización, UDP cerrado, batería agotada, ajuste manual.
El programa práctico es sencillo y cabe en tres pasos. Primero —acostumbrarse a reconocer los errores temporales por el texto: not yet valid, has expired, RequestTimeTooSkewed, jwt expired, jwt not active. Segundo —hacer de la verificación del desfase del reloj el primer punto de cualquier análisis de incidente con proxy: date -u, encabezado Date por HTTP a través del proxy, timedatectl o chronyc tracking. Es un minuto que ahorra horas. Tercero —hacer que el problema no vuelva: chrony con iburst y makestep en las imágenes base, una sola fuente de hora por máquina, sincronización en el host para los contenedores, métrica de desfase en el monitoreo y healthcheck en los servicios que salen a través de un proxy.
Cuando todo esto está hecho, el contacto con el proveedor de proxy se vuelve con sentido: llega con la hipótesis descartada y las salidas de comandos listas, y el análisis va al grano. Y en la mayoría de los casos el contacto simplemente no hará falta —porque el reloj será lo primero que revise y lo último que lo sorprenda.