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
- Fix the system clock (ntpdate/chrony/systemd-timesyncd or `date -s`) so it is after the Unix epoch.
- Enable automatic time sync on the host/VM/container before running the trader.
- 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
- Enable NTP/chrony/systemd-timesyncd on all trading hosts.
- Add a startup health check verifying the clock is past the epoch and roughly correct.
- Monitor clock skew in containers/VMs and alert before it breaks signing.
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
- signature_hex called before sign
- heartbeat request timeout should fit in an instant
- Error calling `SystemTime`
- System time overflow: value exceeds u64::MAX nanoseconds
- in-flight mutex poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/7c6bccd86790190e.
Report an issue: GitHub.