All guides

LEARN \u00b7 DEBUGGING GUIDE

JWT token invalid after deployment: how to debug it

Users were logged in. You deploy. Everyone is logged out. Every JWT returns 'invalid signature' or 'token expired'. The tokens are fine — the server can no longer verify them.

IntermediateAuth/session/payment bugs

What this usually means

JWTs are signed with a secret or private key. The server verifies them using the same secret or the corresponding public key. If the signing key changes between deployments — because of a new env var value, a rotated secret, a different key file path, or a different algorithm — existing tokens become unverifiable. The server sees a valid token structure but the signature does not match what it expects.

( 01 )Fast diagnosis

The first ten minutes \u2014 establish facts before touching code.

  • 1Check the JWT error message exactly. 'invalid signature' means the signing key changed. 'token expired' means clock skew or the token is actually expired. 'invalid algorithm' means the signing algorithm changed.
  • 2Compare the JWT_SECRET or signing key between the old deployment and the new one. Did it change? Was it accidentally rotated or regenerated?
  • 3Decode a JWT without verification (use jwt.io or `jwt.decode(token, verify=False)`). Check the `alg` claim. Does it match what your server expects?
  • 4Check the server clock. If the server time is off by more than the token's expiration buffer, all tokens appear expired.
  • 5Verify the key format. Some libraries expect base64-encoded secrets, others expect raw strings. A format change breaks verification.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchEnvironment variable for JWT_SECRET or signing key — compare staging vs production values
  • searchJWT verification code — algorithm parameter, key loading, clock tolerance settings
  • searchDeployment config — was the secret regenerated or rotated during deploy?
  • searchServer time — `date` on the production server, compare to actual time
  • searchJWT library version — did the deploy update the JWT library to a version with breaking changes?
  • searchKey file path — if using RSA/ECDSA keys, is the file path valid in production?
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningJWT secret was regenerated or changed during deployment
  • warningDifferent JWT_SECRET value in production vs staging (different secret per environment is normal, but existing tokens are signed with the old one)
  • warningAlgorithm mismatch: tokens signed with HS256 but the server expects RS256, or vice versa
  • warningServer clock is wrong — NTP not running, VM time drifted after a pause/migration
  • warningKey format changed: the secret was stored as base64 but the library expects raw bytes
  • warningThe secret is read from a file that does not exist in production (different filesystem path)
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildNever rotate JWT secrets without a transition period. Support two secrets simultaneously: the new one for signing, both the old and new for verification.
  • buildAdd a JWT version claim or key ID (kid) in the token header so the server can select the correct verification key.
  • buildSet a generous clock tolerance (e.g. 30-60 seconds) to handle minor server clock drift.
  • buildLog the JWT error details (invalid signature vs expired vs wrong algorithm) — do not mask them as a generic 401.
  • buildUse asymmetric signing (RS256/ES256) in production so the public key can be shared safely and rotated independently.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedGenerate a JWT with the new secret, send it to the server, and confirm it is accepted.
  • verifiedSend a JWT signed with the old secret. The server should reject it with 'invalid signature' — this confirms the key changed.
  • verifiedCheck the server clock with `date -u` and compare to `https://time.is/UTC`.
  • verifiedDeploy to staging first and verify tokens from before the deploy are still accepted.
  • verifiedWrite an integration test that creates, signs, and verifies a JWT — run it in CI.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningRotating the JWT secret without a transition period and invalidating all active user sessions
  • warningUsing a weak or default secret (`'secret'`, `'changeme'`) that gets overwritten by a platform env var
  • warningNot logging the specific JWT verification error — '401 Unauthorized' is not enough to debug
  • warningAssuming the JWT library handles key format encoding automatically
  • warningSetting an excessively short token expiry that combined with clock skew rejects valid tokens