cloudflare/cloudflared · error

metadata JWT is older than %s

Error message

metadata JWT is older than %s

What it means

validateMetadataIssuedAt enforces freshness of the metadata JWT's `iat` (issued-at) claim. If the token was issued more than metadataMaxAge (24 hours) ago relative to the local clock, GetAppInfo rejects it, because a stale metadata token can no longer be trusted to describe the current Access application.

Source

Thrown at token/token.go:513

	if err != nil {
		return "", errors.Wrap(err, "failed to get app info")
	}
	_ = resp.Body.Close()

	rawJWT := resp.Header.Get(accessMetadataRespHeader)
	if rawJWT == "" {
		return "", fmt.Errorf("failed to find Access application at %s", reqURL)
	}
	return rawJWT, nil
}

func validateMetadataIssuedAt(iat int64, now time.Time) error {
	if iat <= 0 {
		return errors.New("metadata JWT iat is missing or invalid")
	}
	issuedAt := time.Unix(iat, 0)
	if issuedAt.Before(now.Add(-metadataMaxAge)) {
		return fmt.Errorf("metadata JWT is older than %s", metadataMaxAge)
	}
	if issuedAt.After(now.Add(metadataAllowedClockSkew)) {
		return fmt.Errorf("metadata JWT is more than %s in the future", metadataAllowedClockSkew)
	}
	return nil
}

func handleRedirects(req *http.Request, via []*http.Request, orgToken string) error {
	// attach org token to login request
	if strings.Contains(req.URL.Path, AccessLoginWorkerPath) {
		req.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken}) //nolint: gosec
	}

	// attach app session cookie to authorized request
	if strings.Contains(req.URL.Path, AccessAuthorizedWorkerPath) {
		// We need to check and see if the CF_APP_SESSION cookie was set
		for _, prevReq := range via {
			if prevReq != nil && prevReq.Response != nil {

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Synchronize the system clock (systemctl start ntpd / chronyd, or `w32tm /resync` on Windows) and re-run.
  2. Re-run the command so a fresh metadata JWT is fetched from the edge instead of a cached one.
  3. Clear any persisted token cache (e.g. stored Access tokens under the cloudflared config directory) and log in again.
  4. If clock skew is intentional in your environment, this 24h window cannot be extended without changing the library; keep clocks within NTP tolerance.

Example fix

// before (host with skewed clock)
$ date; cloudflared access curl https://app.internal.example.com
// error: metadata JWT is older than 24h0m0s
// after — resync clock, then retry
$ sudo chronyc makestep
$ cloudflared access curl https://app.internal.example.com
Defensive patterns

Strategy: validation

Validate before calling

// check local clock sanity before calling GetAppInfo
now := time.Now()
resp, _ := http.Get("https://www.cloudflare.com/cdn-cgi/trace")
// if you parse a Date header or NTP time and |localNow - serverNow| > 1h, fix the clock first
if skew := time.Since(serverTime); skew > time.Hour || skew < -time.Hour {
    return fmt.Errorf("local clock skewed by %s; synchronize with NTP before fetching metadata", skew)
}

Try / catch

appInfo, err := token.GetAppInfo(authDomain, appURL)
if err != nil {
    if strings.Contains(err.Error(), "older than") {
        return fmt.Errorf("%w — check system clock and clear cached tokens", err)
    }
    return err
}

Prevention

When it happens

Trigger: GetAppInfo parses a metadata JWT whose `iat` is earlier than now minus 24h. Happens when cached or replayed tokens are used, or when the local machine clock is more than 24h ahead of real time so a genuinely fresh token appears old.

Common situations: Machine with a badly skewed system clock (VM restored from snapshot, wrong RTC, NTP disabled); a cached metadata JWT stored on disk and reused the next day; replaying an old token captured from logs.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/9ff18186327ab3c2. Report an issue: GitHub.