Unix Timestamps for JWT and Token Expiry — How It Works

If you've ever decoded a JWT and seen something like "exp": 1744070400, you've encountered Unix timestamps in one of their most common practical uses. The expiry system for JSON Web Tokens is built entirely on Unix time — and once you understand how it works, debugging token issues becomes much simpler.

The Unix Timestamp Converter turns any raw timestamp number into a readable date instantly. This article covers how JWT claims use Unix timestamps, how to calculate expiry times, and where things typically go wrong.

What exp and iat Actually Mean

A decoded JWT payload looks something like this:

{
  "sub": "user_12345",
  "iat": 1743984000,
  "exp": 1744070400,
  "role": "admin"
}

Both iat (issued at) and exp (expiry) are Unix timestamps — the number of seconds since January 1, 1970, 00:00:00 UTC. They have nothing to do with your server's local timezone; they're always UTC-based.

iat: 1743984000 means the token was issued at that specific second in history. exp: 1744070400 means the token becomes invalid at that second. The difference between them — 86,400 seconds — is the token's authorized lifetime. In this case, exactly 24 hours.

To check what date those timestamps represent, paste either number into the Unix Timestamp Converter. 1743984000 resolves to April 7, 2026 00:00:00 UTC. 1744070400 resolves to April 8, 2026 00:00:00 UTC.

How Token Lifetime Is Calculated

The logic is straightforward. When a server issues a token, it:

1. Gets the current Unix timestamp (call it now) 2. Adds the desired lifetime in seconds 3. Sets that sum as the exp claim

For a 1-hour token: exp = now + 3600 For a 24-hour token: exp = now + 86400 For a 7-day token: exp = now + 604800 For a 30-day token: exp = now + 2592000

When a client presents the token, the server checks whether the current timestamp is less than exp. If current_time > exp, the token is rejected as expired.

The math is purely integer arithmetic — no timezone conversions, no calendar logic, no month-length edge cases. That's one of the reasons Unix timestamps work well for this.

Common Token Lifetimes by Use Case

Token typeTypical lifetimeSeconds
Short-lived access token15 minutes900
Standard access token1 hour3,600
API access token24 hours86,400
Refresh token7 days604,800
Long-lived refresh token30 days2,592,000
Remember-me token90 days7,776,000
Email verification link24–72 hours86,400–259,200
Password reset link15–60 minutes900–3,600

Password reset and email verification tokens should be short — they're single-use and sensitive. Refresh tokens can be longer because they're stored securely and can be revoked server-side.

Debugging Token Expiry Issues

When a token is being rejected unexpectedly, the first step is decoding it and checking the exp field. Most JWT libraries have a decode function that doesn't verify the signature — useful for inspection when you just want to read the claims.

Once you have the exp value, paste it into the Unix Timestamp Converter to get the human-readable expiry time. Compare that to the current time. If the token expired three hours ago, you know the issue is a stale token. If the expiry is in the future and the token is still being rejected, the problem is something else — signature mismatch, wrong audience claim, or a clock skew issue.

Clock skew is a common problem in distributed systems. If the issuing server and the validating server have clocks that differ by more than a few seconds, tokens can be rejected as expired when they shouldn't be, or accepted when they should have expired. Most JWT libraries allow a small clock skew tolerance — typically 30 seconds to 5 minutes — for this reason.

The iat claim helps here. If iat is in the future from the validating server's perspective, the issuing server's clock is ahead. If exp - iat doesn't match the expected token lifetime, something went wrong during issuance.

Seconds vs Milliseconds: A Common JWT Bug

Unix timestamps are in seconds. JavaScript's Date.now() returns milliseconds. This mismatch causes bugs that can be hard to spot.

If your Node.js code does this:

const exp = Date.now() + 3600; // Wrong: Date.now() is milliseconds

You've set an expiry of about 1 millisecond into the future (current milliseconds + 3600 milliseconds = essentially now). The token will be rejected immediately.

The correct version:

const exp = Math.floor(Date.now() / 1000) + 3600; // Correct: convert to seconds first

A 13-digit number in a JWT exp field is a dead giveaway that milliseconds were used accidentally. Standard Unix timestamps are 10 digits as of 2026. If you see exp: 1744070400000, that timestamp resolves to the year 57,000-something — a clear signal that milliseconds leaked into a seconds field.

The nbf Claim: Not Before

JWT also supports an nbf (not before) claim, also a Unix timestamp. It specifies the earliest time the token is valid. If current_time < nbf, the token is rejected even if it hasn't expired.

This is useful for tokens that should only become valid in the future — a scheduled access grant, a timed download link, or a token issued ahead of a scheduled event. The combination of nbf and exp defines a precise validity window.

Example: `json { "nbf": 1744070400, "exp": 1744156800 } `

This token becomes valid on April 8, 2026 00:00:00 UTC and expires on April 9, 2026 00:00:00 UTC — a 24-hour window starting in the future.

Revoking Tokens Before Expiry

One limitation of stateless JWT design is that you can't revoke a token before its exp time without maintaining server-side state. If a user logs out or a token is compromised, the token technically remains valid until it expires — unless you implement a blocklist.

Common approaches:

  • Short-lived access tokens + refresh tokens: Keep access tokens short (15 minutes) so they expire quickly. Revoke refresh tokens on logout. The access token will expire soon on its own.
  • Token blocklist: Store revoked token IDs (usually the jti claim) server-side until their exp passes. More overhead, but allows immediate revocation.
  • Token rotation: Issue a new access token with every refresh request and invalidate the old one.

The Unix timestamp in exp is the outer bound of validity. Your application logic determines whether a token within that window should actually be honored.

Reading Token Expiry in Practice

If you're debugging an API and want to quickly check whether a token is expired, you don't need a full JWT library. Decode the payload (the middle segment of the JWT) from base64, parse the JSON, and check the exp field against the current Unix timestamp.

In a browser console: `javascript const payload = JSON.parse(atob(token.split('.')[1])); console.log('Expires:', new Date(payload.exp * 1000).toISOString()); console.log('Expired:', Date.now() / 1000 > payload.exp); `

Or just paste the exp value into the Unix Timestamp Converter to get the readable date without writing any code. That's usually the fastest way to answer the question "has this token expired?" when you're debugging in production.

Related articles