nautechsystems/nautilus_trader · error

Trusted host clock precedes the Unix epoch

Error message

Trusted host clock precedes the Unix epoch

What it means

`current_unix_secs` reads the host clock via `SystemTime::now().duration_since(UNIX_EPOCH)` and fails if the system time is before the Unix epoch. The client trusts the host clock for expiry/scan computations, so a pre-epoch clock is treated as a fatal environment error rather than producing a negative/underflowing timestamp.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:3585

    fn release_slot(&self) {
        *self.in_flight.lock() = None;
    }
}

fn replacement_scan_range(from_block: u64, head_block: u64) -> anyhow::Result<RangeInclusive<u64>> {
    anyhow::ensure!(
        head_block >= from_block,
        "Canonical head {head_block} is behind execution creation block {from_block}"
    );
    let max_end = from_block.saturating_add(MAX_REPLACEMENT_SCAN_BLOCKS - 1);
    Ok(from_block..=head_block.min(max_end))
}

fn current_unix_secs() -> anyhow::Result<u64> {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|_| anyhow::anyhow!("Trusted host clock precedes the Unix epoch"))
        .map(|duration| duration.as_secs())
}

fn validate_payload_operation_batch_size(batch_size: usize) -> anyhow::Result<i64> {
    anyhow::ensure!(
        (1..=MAX_PAYLOAD_OPERATION_BATCH_SIZE).contains(&batch_size),
        "Payload operation batch size must be between 1 and {MAX_PAYLOAD_OPERATION_BATCH_SIZE}"
    );
    Ok(i64::try_from(batch_size).expect("bounded payload batch size fits i64"))
}

fn current_execution_hash(
    intent_id: i64,
    hashes: &[ExecutionTransactionHashRow],
) -> anyhow::Result<&ExecutionTransactionHashRow> {
    let mut current = hashes.iter().filter(|row| row.current);
    let row = current
        .next()

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Fix the host clock: run NTP sync (`timedatectl set-ntp true`, `chronyc makestep`).
  2. Manually set the date (`date -s @<epoch>`) if NTP is unavailable.
  3. Add clock sanity checks to startup health checks before launching the trader.

Example fix

// before: host clock at 1969
SystemTime::now().duration_since(UNIX_EPOCH) -> Err
// after: sync host
$ sudo timedatectl set-ntp true && timedatectl status
Defensive patterns

Strategy: validation

Validate before calling

let now = SystemTime::now().duration_since(UNIX_EPOCH)
    .map_err(|_| anyhow::anyhow!("host clock invalid; run NTP sync before starting"))?;
anyhow::ensure!(now.as_secs() > 1_600_000_000, "host clock implausibly old");

Try / catch

match current_unix_secs() {
    Err(e) if e.to_string().contains("precedes the Unix epoch") => {
        sync_host_clock()?;
        current_unix_secs()
    }
    other => other,
}

Prevention

When it happens

Trigger: Any code path calling `current_unix_secs()` (e.g. replacement scans, expiry checks) on a host whose wall clock is set before 1970-01-01 — typically a VM without RTC after power loss, or an embedded/board with uninitialized clock.

Common situations: Freshly provisioned VPS/containers with unset system time; Raspberry Pi or IoT boards without battery-backed RTC; misconfigured NTP; test VMs with frozen clocks.

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/7215ffe3bc768524. Report an issue: GitHub.