cloudflare/cloudflared · error

metadata JWT is more than %s in the future

Error message

metadata JWT is more than %s in the future

What it means

validateMetadataIssuedAt also rejects JWTs whose `iat` is in the future by more than metadataAllowedClockSkew. A metadata token claiming to be issued significantly after the current time is treated as invalid, protecting against tokens minted with bad clocks or forged timestamps.

Source

Thrown at token/token.go:516

	_ = 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 {
				for _, c := range prevReq.Response.Cookies() {
					if c.Name == appSessionCookie {
						req.AddCookie(&http.Cookie{Name: appSessionCookie, Value: c.Value}) //nolint: gosec

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Synchronize the local clock with NTP (chronyd/systemd-timesyncd, `w32tm /resync` on Windows).
  2. For VMs/containers, enable the hypervisor/host clock-sync feature and resume-pause clock catch-up.
  3. Re-run the command after the clock is correct; the token itself is fine.
  4. Verify no test/mocked time source (e.g. faketime, frozen CLOCK_REALTIME) is in effect for the process.

Example fix

// before
$ cloudflared access login https://myteam.cloudflareaccess.com
// error: metadata JWT is more than 5m0s in the future
// after — fix local clock, then retry
$ sudo timedatectl set-ntp true && sudo chronyc makestep
$ cloudflared access login https://myteam.cloudflareaccess.com
Defensive patterns

Strategy: validation

Validate before calling

// detect a lagging local clock before calling GetAppInfo
resp, err := http.Head("https://www.cloudflare.com")
if err == nil {
    serverDate, _ := http.ParseTime(resp.Header.Get("Date"))
    if skew := time.Since(serverDate); skew < -time.Minute {
        return fmt.Errorf("local clock is %s behind server time; enable NTP", -skew)
    }
}

Try / catch

appInfo, err := token.GetAppInfo(authDomain, appURL)
if err != nil {
    if strings.Contains(err.Error(), "in the future") {
        return fmt.Errorf("%w — local clock is behind; resynchronize with NTP and retry", err)
    }
    return err
}

Prevention

When it happens

Trigger: GetAppInfo parses a metadata JWT whose `iat` exceeds now + metadataAllowedClockSkew. Typically caused by the local machine's clock running behind real time (slow clock) while the edge issued the token with the correct current timestamp.

Common situations: Laptop/desktop with drifting RTC or paused VM whose clock fell behind; container with wrong timezone/clock after host suspend; NTP not running so local time lags the Cloudflare edge by minutes or hours.

Related errors


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