nautechsystems/nautilus_trader · critical

Time went backwards

Error message

Time went backwards

What it means

TimeNonce::now_millis reads the system clock and expects it to be after the Unix epoch (1970-01-01 UTC). If the OS clock reports a time before the epoch, duration_since fails and the expect panics. Nonce generation for signed Hyperliquid requests must be monotonic wall-clock milliseconds, so a backwards clock makes signing impossible.

Source

Thrown at crates/adapters/hyperliquid/src/signing/nonce.rs:51

    /// Create from Unix milliseconds.
    pub fn from_millis(ms: i128) -> Self {
        Self(ms)
    }

    /// Get as milliseconds.
    pub fn as_millis(self) -> i128 {
        self.0
    }

    /// Current time in milliseconds.
    ///
    /// # Panics
    ///
    /// Panics if the system time is before the Unix epoch.
    pub fn now_millis() -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards");
        Self::from_millis(now.as_millis() as i128)
    }
}

impl Display for TimeNonce {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Nonce policy configuration for Hyperliquid.
#[derive(Debug, Clone)]
pub struct NoncePolicy {
    pub past_ms: i64,
    pub future_ms: i64,
    pub keep_last_n: usize,
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Fix the system clock (ntpdate/chrony/systemd-timesyncd or `date -s`) so it is after the Unix epoch.
  2. Enable automatic time sync on the host/VM/container before running the trader.
  3. Upgrade/patch if the platform provides a clock that can legitimately regress; otherwise monitor clock before starting.

Example fix

// before
let nonce = TimeNonce::now_millis(); // panics if clock < epoch

// after (guard at startup)
let now = SystemTime::now().duration_since(UNIX_EPOCH)
    .expect("system clock must be after Unix epoch; fix host time sync");
Defensive patterns

Strategy: validation

Validate before calling

// startup guard
let epoch_ok = SystemTime::now().duration_since(UNIX_EPOCH).is_ok();
if !epoch_ok { panic!("fix system clock before starting trader"); }

Type guard

fn clock_is_sane() -> bool {
    SystemTime::now().duration_since(UNIX_EPOCH).is_ok()
}

Try / catch

// boundary: convert panic to error at FFI/task edge
std::panic::catch_unwind(|| TimeNonce::now_millis())
    .map_err(|_| anyhow!("system clock before Unix epoch"))

Prevention

When it happens

Trigger: Calling now_millis (directly or via any order/signing flow) while the host system clock is set before 1970-01-01T00:00:00Z, e.g. after RTC battery failure, VM resume with unsynced clock, or container with wrong time source.

Common situations: Embedded/bare-metal hosts without RTC; Docker containers starting before NTP sync; VM snapshots restoring an old clock; misconfigured system timezone/date during testing.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/7c6bccd86790190e. Report an issue: GitHub.