← Blog
February 18, 2026 · 7 min

JWT Expiration and Clock Skew Explained

The exp and nbf claims look simple until server clocks drift. Here's how to set expiration correctly and tolerate clock skew without weakening security.

The claims involved Three registered claims control a JWT's validity window:

  • exp (expiration time) — a Unix timestamp after which the token must be rejected
  • nbf (not before) — the token isn't valid until this time
  • iat (issued at) — when the token was created, useful for auditing and computing age

All three are Unix timestamps in seconds, not milliseconds — a classic off-by-1000x bug.

Why expiration matters A JWT can't be revoked the way a session can (there's no server-side record to delete, by design). Expiration is the primary mechanism limiting how long a leaked or stolen token remains useful. Short expirations (5–15 minutes for access tokens) shrink the blast radius of a leak dramatically.

What clock skew actually is Clock skew is the small, usually unavoidable difference between the system clock on the machine that issued a token and the clock on the machine verifying it. Even NTP-synced servers can drift by a few seconds. If your verifier does an exact, zero-tolerance comparison against `exp` or `nbf`, tokens can be rejected moments before they should logically expire, or accepted moments after — or worse, a token can appear to arrive "before it was issued" if `nbf` is checked too strictly right after issuance.

The standard fix: clock tolerance Most JWT libraries (`jsonwebtoken` in Node, `PyJWT` in Python, `jose` libraries generally) support a **clockTolerance** or **leeway** option, typically set to somewhere between 5 and 60 seconds. This adds a small grace window to `exp` and `nbf` comparisons without meaningfully weakening security.

  • In jsonwebtoken: pass { clockTolerance: 30 } to jwt.verify.
  • In PyJWT: pass leeway=30 to jwt.decode.

How much tolerance is reasonable? - **5–10 seconds** is enough for well-synced infrastructure (same cloud region, NTP enabled). - **30–60 seconds** is a reasonable ceiling for distributed systems across regions or providers. - Anything beyond a couple of minutes usually signals a real clock problem worth fixing at the infrastructure level, not papering over with leeway.

Don't confuse leeway with long expiration Clock tolerance is about tolerating small drift, not extending the token's useful life. A token with `exp` five minutes in the future and 30 seconds of leeway is still, for practical purposes, a five-minute token. Don't use leeway as a backdoor way to get longer-lived tokens.

Testing clock skew locally You can deliberately test this by issuing a token with an `exp` a few seconds in the past and confirming your verifier rejects it appropriately, then adding leeway and confirming it's accepted within tolerance. This is worth a unit test in any auth-critical codebase.

Checklist - Always set `exp` — never issue a token with no expiration. - Store all time-based claims as Unix seconds, not milliseconds. - Configure a small clock tolerance (5–30 seconds) on your verifier, not zero. - Sync your servers' clocks with NTP regardless of application-level leeway. - Don't use `nbf` unless you have a genuine reason to delay a token's validity.