The NATS server is rejecting client connections because the authentication token provided by the client has expired.

Common causes and fixes:

  1. Client Token Generation Timestamp is Incorrect: The most frequent culprit is that the token itself was generated with a system clock that is significantly out of sync with the NATS server’s clock. NATS uses JWTs with an exp (expiration) claim. If the client’s clock is ahead of the server’s, a token valid now from the client’s perspective will appear expired to the server.

    • Diagnosis: On the client machine, run date -u. Compare this to the server’s time (ssh your_nats_server 'date -u'). Significant discrepancies (more than a few seconds) point to a clock sync issue.
    • Fix: Ensure Network Time Protocol (NTP) is configured and running on all client machines. On most Linux systems, this involves installing ntp or chrony and ensuring the service is enabled and started. For example, on a Debian/Ubuntu system:
      sudo apt update
      sudo apt install ntp
      sudo systemctl enable ntp
      sudo systemctl start ntp
      
      This synchronizes the client’s clock with reliable time sources, making token expiration checks accurate.
    • Why it works: NTP actively corrects the system clock, ensuring that the exp claim in the JWT is evaluated against a consistent and accurate timestamp on both the client and server.
  2. JWT exp Claim Too Short or Incorrectly Set: The exp claim in the generated JWT is set to a time that is too soon, or it’s calculated incorrectly, leading to premature expiration. This is common if tokens are generated programmatically without careful consideration of their intended lifespan.

    • Diagnosis: Inspect the JWT itself. You can decode it using an online JWT decoder or programmatically. Look for the exp field. If you generated it, check your generation logic. For example, if you used a library and added a duration, ensure that duration is sufficiently long.
    • Fix: When generating the JWT, set the exp claim to a future timestamp that is reasonably far out, considering the expected operational lifetime of the token. For instance, to set an expiration one year from now:
      import (
          "time"
          "github.com/golang-jwt/jwt/v4"
      )
      
      // ... inside your token generation function
      now := time.Now()
      expirationTime := now.Add(365 * 24 * time.Hour) // 1 year from now
      
      claims := &jwt.StandardClaims{
          ExpiresAt: expirationTime.Unix(),
          // ... other claims
      }
      
      token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
      // ... sign token
      
    • Why it works: By extending the exp claim’s value, you are giving the token a longer validity period, preventing it from being considered expired by the NATS server prematurely.
  3. NATS Server token Authentication Configuration is Incorrect: The NATS server might be configured to expect tokens with specific properties or might have its own internal clock issues that are causing it to misinterpret expiration times, even if the client’s clock is accurate.

    • Diagnosis: Review the NATS server configuration file (e.g., nats-server.conf). Look for the auth section, specifically token or jwt directives. Ensure the issuer and audience (if used) in the JWT match the server’s expectations.
    • Fix: If the server is configured for JWT authentication, ensure the signing_key in the NATS server configuration matches the private key used to sign the JWTs. If you are using token authentication (a simple shared secret), ensure the token value matches exactly. For JWTs, the server config might look like this:
      # nats-server.conf
      auth_users:
        - user: "user"
          password: "password" # If using basic auth alongside JWT
          # OR if using JWT directly for authentication
          # no_password: true # if only using JWT
          # jwt_file: "/etc/nats/jwt/user.jwt" # static JWT file
          # OR dynamic JWT validation
          # jwt_nkey_file: "/etc/nats/keys/nats.nk" # Public key for validation
      # ...
      # If using Nkeys for signing and validation
      # nkey_seed: "/etc/nats/keys/nats.nk" # Server's private key to sign, or public key to verify
      # For JWT validation, you'd typically have a public key the server trusts
      # to verify JWTs signed by a corresponding private key.
      # The most common setup is using Nkeys or a shared secret.
      # If JWTs are signed by a private key, the server needs the corresponding public key.
      # Example with Nkeys for signing/validation:
      # nkey_seed: "/path/to/server/private_nkey.nk" # NATS server's private Nkey
      # auth_users:
      #   - user: "user_public_nkey_from_client_jwt"
      #     permissions:
      #       allow_sub: ["my.topic"]
      #       allow_pub: ["my.topic"]
      # The client would generate a JWT signed by its private Nkey, and the server
      # would have the client's public Nkey to verify.
      
      If you are using simple token authentication (not JWT), the NATS server configuration would look like:
      # nats-server.conf
      listen: 0.0.0.0:4222
      auth_users:
        - user: "myuser"
          password: "mypassword" # This is the shared secret token
      
      And clients would connect with --auth myuser:mypassword.
    • Why it works: Correctly configuring the server ensures it has the right keys or secrets to validate incoming tokens and correctly interpret their claims, including expiration.
  4. NATS Server token Expiration (less common, depends on config): While JWTs have explicit exp claims, some NATS server configurations might have their own session or token-specific timeouts that can be shorter than the JWT’s exp.

    • Diagnosis: Check the NATS server configuration for any timeout settings related to client connections or authentication. Parameters like ping_interval and ping_timeout can indirectly affect perceived connection health, but a direct "token timeout" is less common outside of JWTs. If you’re using a proprietary token mechanism or a very old NATS version, this might be relevant.
    • Fix: Increase any relevant timeout values in the NATS server configuration. For example, if ping_timeout is set too low (e.g., 5 seconds), and the network has latency, a valid token might be dropped because the server thinks the client is dead.
      # nats-server.conf
      ping_interval: 30s
      ping_timeout: 60s
      
    • Why it works: Longer timeouts give the client more grace period to maintain its connection and keep its valid token active in the server’s eyes, especially under variable network conditions.
  5. NATS Server Clock Skew (less common than client): While less frequent, the NATS server’s own clock could be significantly out of sync, causing it to incorrectly evaluate the exp claim.

    • Diagnosis: Run date -u on the NATS server and compare it to a reliable external time source (e.g., curl -I https://google.com/ and look at the Date header).
    • Fix: Ensure NTP is configured and running on the NATS server.
      sudo systemctl enable chronyd # or ntp
      sudo systemctl start chronyd # or ntp
      
    • Why it works: A synchronized server clock ensures that the exp claim in the JWT is evaluated against the correct current time.
  6. Token Revocation: If you are using a system that supports token revocation (e.g., via a custom authentication plugin or by reissuing tokens with updated revocation lists), the token might have been explicitly revoked even if it hasn’t expired.

    • Diagnosis: This is highly dependent on your token management system. Check logs for any revocation events. If you are manually managing tokens, verify if the specific token ID or user associated with it has been marked for revocation.
    • Fix: Reissue a new, valid token for the client. If revocation is managed by a central system, ensure that system is functioning correctly and that the revoked token is no longer being presented.
    • Why it works: Revocation explicitly invalidates a token before its exp date, and issuing a new token bypasses the revoked one.

The next error you’ll likely encounter after fixing authentication issues is a "Permissions Denied" error if the user identified by the token does not have the necessary permissions to publish or subscribe to the requested subjects.

Want structured learning?

Take the full Nats course →