Reported symptoms
One host out of nine starts rejecting logins. The application log is specific and completely misleading:
ERROR auth: token validation failed: token used before issued
(iat=2026-08-11T14:02:07Z, now=2026-08-11T13:58:12Z)
ERROR upstream: tls: failed to verify certificate: x509: certificate
has expired or is not yet valid: current time 2026-08-11T13:58:12Z
is before 2026-08-11T14:01:44Z
The team pages the identity platform team, then re-issues an internal certificate that was not broken. Both errors name a remote component. Neither is about a remote component.
Diagnosis
Two numbers in that error message are the answer, and they are sitting
next to each other. now is four minutes behind iat, and the
certificate notBefore is in the future. A clock disagrees with the
rest of the estate, and only one clock is involved.
Ask the container and the host the same question:
docker exec app date -u
date -u
If those agree with each other and disagree with the world, the container is not the problem and never could have been:
timedatectl
chronyc tracking
Local time: Tue 2026-08-11 13:58:12 UTC
System clock synchronized: no
NTP service: inactive
Reference ID : 00000000 ()
Stratum : 0
Leap status : Not synchronised
System time : 243.881 seconds fast of NTP time
Four minutes fast, no sync, no NTP service running. Correlate with the change log: this VM was restored from a snapshot the previous evening, and chronyd was not restarted afterwards.
Confirm that there is no container-level clock to fix:
docker exec app readlink /proc/1/ns/time
readlink /proc/1/ns/time
Both return the same inode. The container shares the host’s time namespace — and even if it did not, that would not help.
Resolution path
- Confirm the host has a reachable time source before you change anything.
chronyc sources -v— a host that cannot reach any NTP server will not stay fixed, and stepping it manually just resets the countdown.- If every source is unreachable, this is a firewall or routing finding (UDP 123, or NTS on TCP 4460) and belongs to the network team.
- Start the time service.
systemctl enable --now chrony(orchronyd/systemd-timesyncd, per distribution).- Decide between slewing and stepping.
- Slewing corrects gradually and never moves time backwards; it takes roughly 20 minutes per second of offset, so four minutes takes far too long here.
sudo chronyc makestepapplies the correction immediately.- Restart processes that cached the wrong time.
- Some TLS stacks, token caches and schedulers read the clock once.
docker compose restartthe affected services after the step, in a rolling fashion. - Remove the NTP client from the image.
- It has never worked and it hides the fact that nothing is managing time. Delete the package and the entrypoint call, and rebuild.
- Close the loop on the snapshot restore.
- Add "verify timedatectl output" to the VM restore runbook, and enable the hypervisor guest time-sync or RTC handling for this guest.
Verification
- The host is synchronised.
timedatectlreportsSystem clock synchronized: yesand a running NTP service. - The offset is small and stable.
chronyc trackingshows Leap status Normal and a system-time offset in milliseconds, re-checked five minutes later. - The container agrees.
docker exec app date -umatches an external reference. - The original errors are gone with no change to the identity provider, the CA, or any certificate — that is what proves the diagnosis rather than a coincidence.
- The rest of the fleet is checked. Run the same
timedatectlcheck on every host; a snapshot restore is rarely done to exactly one machine.
Prevention
- Alert on clock sync state, not just on clock offset.
timedatectlreportingSystem clock synchronized: nois a binary, unambiguous precursor to this incident and is trivially scrapeable vianode_timex_sync_status. - Never install an NTP client in a container image. It cannot work, and its presence convinces the next reader that time is handled.
- Put a time check in the VM restore and migration runbooks. Snapshot restore, suspend/resume and live migration are the three events that produce this failure, and all three are planned work with a checklist attached.
- Give token validation a small leeway. Thirty seconds of tolerance on
nbfandiatcosts nothing in security terms and converts routine sub-minute skew from an outage into a non-event.