Keycloak ‘Token Not Active’ Clock Skew: How to Fix

Guilliano Molaire Guilliano Molaire 10 min read

Last updated: July 2026

A “Token is not active” or “JWT issued in the future” error when the token clearly has not expired is almost always clock skew: the clock on the Keycloak server and the clock on your API or resource server differ by more than the allowed tolerance, so a freshly issued token’s nbf or iat looks invalid to the recipient. The fix is to synchronize both clocks with NTP; as a stopgap while you arrange that, configure a small allowed clock skew or leeway value in your token-validation library or in Keycloak’s identity-provider brokering settings. Keep the leeway small — seconds, not minutes — and treat it as a temporary measure, not a permanent solution.


How JWT time claims work

Every token Keycloak issues contains three time-related claims encoded as Unix timestamps (seconds since 1970-01-01 00:00:00 UTC):

Claim Name Meaning
iat Issued At The moment Keycloak signed the token
nbf Not Before The earliest moment the token is valid for use
exp Expiration The moment the token becomes invalid

A validating party — your API, a reverse proxy, or another identity provider — checks all three claims against its own clock. If any check fails, the token is rejected before any further processing happens.

When things work correctly, iat is slightly in the past, nbf is at or before the current moment, and exp is in the future. Clock skew upsets that picture: if the validating server’s clock is ahead of Keycloak’s clock by even a few seconds, a brand-new token can have an iat that appears to be in the future, making it look fraudulent.

You can inspect these raw values at any time using the JWT Token Analyzer — paste a raw access or ID token and the tool decodes the claims into human-readable timestamps without sending the token anywhere.


Why clock skew produces false rejections

Consider this scenario:

  • Keycloak issues a token at 12:00:00 UTC (by its clock).
  • The token has iat: 1750000000, nbf: 1750000000, exp: 1750003600.
  • Your API server’s clock reads 12:00:05 UTC — five seconds ahead.
  • Your API receives the token at 12:00:01 UTC (by Keycloak’s clock, i.e., 12:00:06 UTC by the API clock).

From the API’s perspective, the token was issued at 1750000000, but the API’s “current time” is already 1750000005. Normally that is fine. However, if your validation library or middleware strictly requires that iat <= now with zero tolerance, it may also verify that iat does not appear unreasonably close to now based on the library’s own heuristics.

More commonly, the failure mode runs the other direction: if the Keycloak server’s clock is ahead of the API server’s clock, then when the API receives a freshly issued token, the iat and nbf fields contain a timestamp that is in the API’s future. The validator concludes the token was issued in the future and rejects it outright with an error such as:

  • Token is not active
  • JWT not yet valid
  • iat is in the future
  • Token was issued in the future
  • invalid_token: The access token provided is expired, revoked, malformed, or invalid for other reasons

In Keycloak’s own logs you may see Token is not active if Keycloak is acting as a resource server or if you are using a token introspection flow. The error message rarely says “clock skew,” which is why this issue is easy to misdiagnose.

Related: if you are dealing with expired-token errors that are not caused by clock skew, see Keycloak token expired troubleshooting for a broader look at expiration-related failures.


Symptom, cause, and fix at a glance

Symptom Root cause Fix
Token is not active on a freshly issued token nbf or iat appears to be in the API server’s future because Keycloak’s clock is ahead Sync Keycloak host clock with NTP; add small leeway in the validator
JWT not yet valid Same — nbf in the future from the API’s perspective Same
iat is in the future Keycloak host clock is ahead of the validator’s clock Sync clocks; reduce or verify leeway
Token is expired on a brand-new token API server clock is far ahead, making exp appear to have passed Sync API host clock backward (via NTP); do not set clock manually
Token works for some requests, fails intermittently Multiple load-balanced API nodes with different clock drift Sync all nodes uniformly with a single NTP source
Token fails after crossing a service boundary (e.g., a microservice calls another) Each hop has a slightly different clock Ensure every host and container in the chain uses NTP

The real fix: synchronize clocks with NTP

The only permanent fix for clock skew is time synchronization. Every host involved in issuing or validating tokens — the Keycloak server, every API/resource server, every reverse proxy — must be synchronized to a reliable time source.

chronyd is the default NTP daemon on modern RHEL, Fedora, Debian, and Ubuntu systems. It handles irregular network conditions and VM time drift better than the older ntpd.

# Install chrony if not present
sudo apt-get install -y chrony      # Debian / Ubuntu
sudo dnf install -y chrony          # RHEL 9 / Fedora

# Start and enable
sudo systemctl enable --now chronyd

# Check synchronization status
chronyc tracking

The chronyc tracking output shows System time offset from the reference clock. An offset below 10 milliseconds is excellent. Anything above a few seconds indicates a problem.

Docker containers

Containers share the kernel clock with their host — they do not maintain an independent clock. This means:

  • If the Docker host is synchronized with NTP, containers are automatically synchronized.
  • If the Docker host drifts, all containers drift equally.

The correct place to fix clock skew for containers is the host OS. You do not need NTP inside the container itself. Verify with:

docker run --rm busybox date -u
date -u

Both commands should return times within milliseconds of each other.

Virtual machines and cloud instances

VMs are the most common source of clock drift in practice. Hypervisors can suspend and resume VMs, causing the guest clock to lag behind real time by minutes. Cloud instances (AWS EC2, GCP, Azure VMs) typically have NTP pre-configured, but the service can be disabled or misconfigured.

On AWS, the recommended time source is the Amazon Time Sync Service at 169.254.169.123. On GCP, it is metadata.google.internal. On Azure, the hypervisor provides time directly to guests via the VMBus channel, supplemented by time.windows.com.

Always confirm your cloud instance has NTP active:

timedatectl status

Look for NTP service: active and System clock synchronized: yes.

Checking actual skew between two hosts

The fastest way to measure skew between the Keycloak server and an API server is to compare their UTC clocks directly:

# On Keycloak host
date -u

# On API/resource server host
date -u

A difference of more than one second warrants investigation. A difference of more than five seconds will cause intermittent failures with strict validators.


Stopgap: configure allowed clock skew

While you are arranging NTP synchronization, you can configure a tolerance window — often called “allowed clock skew” or “leeway” — so that tokens within a small time window are still accepted despite the drift.

Keycloak identity-provider brokering (“Allowed clock skew”)

When Keycloak acts as a service provider consuming tokens or assertions from an external OIDC or SAML identity provider, it exposes an “Allowed clock skew” setting per identity-provider configuration:

  1. Go to Realm Settings > Identity Providers > (your IdP) > Settings.
  2. Scroll to the Allowed clock skew field (seconds).
  3. Set a value between 30 and 60 seconds as a temporary stopgap.

This setting applies only to Keycloak’s inbound validation of external IdP tokens or SAML assertions, not to the tokens Keycloak itself issues. If your problem is with tokens Keycloak issues, this setting has no effect on client-side validation.

Client-side validators

Configure leeway in the library your API uses to validate tokens:

Node.js — jose

import { jwtVerify } from 'jose';

const { payload } = await jwtVerify(token, publicKey, {
  clockTolerance: 30, // seconds
});

Java — jjwt (io.jsonwebtoken)

Jwts.parser()
    .clockSkewSeconds(30)
    .verifyWith(publicKey)
    .build()
    .parseSignedClaims(token);

Python — PyJWT

import jwt

payload = jwt.decode(
    token,
    public_key,
    algorithms=["RS256"],
    leeway=30,  # seconds
)

Spring Security (spring-security-oauth2-resource-server)

Spring’s JWT decoder does not expose a direct leeway property in application.yml. Configure it programmatically:

@Bean
public JwtDecoder jwtDecoder() {
    NimbusJwtDecoder decoder = NimbusJwtDecoder
        .withJwkSetUri("https://your-keycloak/realms/myrealm/protocol/openid-connect/certs")
        .build();

    OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>(
        new JwtTimestampValidator(Duration.ofSeconds(30))
    );
    decoder.setJwtValidator(withClockSkew);
    return decoder;
}

For a deeper look at how token validation works across different environments, see Keycloak token validation for APIs.


Why leeway must stay small

Setting a leeway of 30 to 60 seconds is a reasonable stopgap. Setting it to 10 minutes is not — it defeats the purpose of short-lived tokens entirely. A 10-minute leeway means an attacker who obtains a token has a 10-minute window beyond its stated exp in which it remains accepted.

The security contract of short-lived JWTs depends on validators rejecting them promptly after exp. For a thorough treatment of token lifecycle strategy, see JWT token lifecycle management, expiration, refresh, and revocation strategies.

Keep leeway at 30 seconds or less. Once NTP is synchronized and verified, remove the leeway configuration or reduce it to 5 seconds.


Diagnosing clock skew step by step

  1. Decode the failing token. Use the JWT Token Analyzer or python3 -c "import base64,json,sys; p=sys.argv[1].split('.')[1]; p+='='*(4-len(p)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p)),indent=2))" <token> to read iat, nbf, and exp as Unix timestamps.

  2. Convert the timestamps. Run date -d @<timestamp> -u on Linux or date -r <timestamp> on macOS to convert a Unix timestamp to human-readable UTC.

  3. Compare to both servers’ clocks. Run date -u on the Keycloak host and on the API host. Note any discrepancy.

  4. Check chrony or NTP status. Run chronyc tracking or timedatectl status on each host.

  5. Look at Keycloak admin events. In the Keycloak Admin Console, go to Events > Admin Events and filter for token-related operations. In Events > Events, filter by LOGIN_ERROR and look for error=token_not_active.

  6. Check your validator logs. Look for the exact claim that failed. Libraries like jose and jjwt log the specific claim and the computed delta, which makes it easy to confirm that skew is the cause.

If the error only appears after identity-provider brokering (Keycloak consuming tokens from another IdP), the skew may be between your Keycloak host and the external IdP’s host rather than between Keycloak and your own API. See Keycloak invalid_grant error troubleshooting for more on brokering errors.


Clock skew in production: a checklist

Before going to production with any Keycloak deployment, verify the following:

  • All Keycloak cluster nodes are synchronized to the same NTP source and show an offset below 100 milliseconds.
  • All resource servers and API gateways are synchronized to the same or a peered NTP source.
  • Docker host clocks are verified; container clocks are not checked independently.
  • VM guests have NTP active, not just the hypervisor time source (use both where possible).
  • Any leeway or clockTolerance settings in client libraries are documented and set to 30 seconds or less.
  • Monitoring alerts exist for chronyc tracking showing an offset above 1 second.

For broader JWT security guidance, see JWT best practices for developers.


Managed Keycloak without the clock headaches

Configuring and maintaining NTP synchronization across Keycloak nodes, database hosts, and API servers is one of the operational details that is easy to overlook until it causes a production incident at 2 a.m. Skycloak’s managed Keycloak service handles infrastructure-level concerns — including time synchronization, node health, and cluster consistency — so your team can focus on building features rather than debugging token timestamps.

See Skycloak pricing and plans


Frequently asked questions

Why does Keycloak say my token is not active?

“Token is not active” usually means one of three things: the token’s nbf (not-before) timestamp is in the future relative to the validating server’s clock, the token’s iat (issued-at) timestamp appears to be in the future, or the token was issued for a session that Keycloak has since invalidated (logout or session timeout). If the token is brand new and you did not explicitly revoke it, clock skew is almost always the cause. Compare date -u between the Keycloak host and the server doing the validation to confirm.

What is “Allowed clock skew” in Keycloak?

“Allowed clock skew” is a per-identity-provider setting in Keycloak’s Admin Console that adds a tolerance window when Keycloak validates tokens or SAML assertions received from an external IdP. It appears under the identity provider’s configuration in the realm settings and is expressed in seconds. It applies only to inbound validation from that specific external IdP — it does not affect how tokens Keycloak itself issues are validated by downstream clients.

How much clock skew should I allow?

Set leeway to the smallest value that eliminates false rejections in your environment. In practice, 30 seconds covers the vast majority of real-world NTP drift and network latency. Values above 60 seconds indicate a systemic clock-synchronization problem that should be fixed rather than worked around. Never set leeway above 300 seconds (5 minutes) in a production system, and treat anything above 60 seconds as a red flag requiring investigation.

Does clock skew affect refresh tokens?

Refresh tokens are typically opaque (not JWTs) in Keycloak, so clock skew does not affect their validation directly. However, if you exchange a refresh token for a new access token, the new access token’s iat and nbf are set by the Keycloak server’s clock. If your API server’s clock is ahead of Keycloak’s, the freshly issued access token from the refresh will be rejected with the same “not active” error as any other token. Fix the underlying clock synchronization to resolve both cases. For more on refresh token behavior, see JWT token lifecycle management, expiration, refresh, and revocation strategies.

Can containers have a different clock than the host?

No — Docker containers share the host kernel’s clock. A container cannot have a different time than its host. However, if you run Keycloak in a container and your API in a separate VM or bare-metal host, those two hosts have independent clocks and can drift relative to each other. Always synchronize every host in the chain with NTP; do not assume containers are automatically in sync with other machines in your infrastructure just because they share the clock with their own host.

Tired of running Keycloak yourself?

Skycloak runs real upstream Keycloak for you with a 99.99% SLA. No fork, no lock-in, just managed Keycloak that stays patched and on call so you don't have to.

Guilliano Molaire
Written by
Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Start Free Trial Talk to Sales
© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman