A disconnected user is a non-system account user credential that has no active client connections at the current observation epoch. The user exists in the account’s configuration or JWT — it can authenticate and connect — but nothing is currently using it.
Every user credential is a key to your NATS deployment. A disconnected user means a valid key exists that nobody is currently using. In security terms, unused credentials are unmonitored credentials. If a user JWT or NKey is compromised, the attacker gets authenticated access to the account’s subject namespace, and there’s no legitimate traffic pattern to compare against for anomaly detection. The connection would be the only one — and it would look normal.
Credential sprawl is the operational manifestation. Organizations that create user credentials liberally — per developer, per test run, per service instance — accumulate hundreds of valid credentials over time. Most stop being used within weeks as developers rotate, services are redeployed, or test environments are torn down. Without active pruning, the credentials persist. Each one is a potential access vector, a line item in security audits, and a source of confusion when operators try to understand who can connect to what.
The problem is compounded in JWT-based deployments where user credentials are self-contained. A user JWT file sitting on a developer’s laptop, in a CI/CD pipeline artifact, or in an old container image remains valid until explicitly revoked — regardless of whether anyone intends to use it. Unlike centralized auth systems where disabling an account immediately locks out all users, NATS JWTs are bearer credentials that work until the account operator takes explicit action.
Service was retired without revoking its credentials. A microservice that connected to NATS was decommissioned. The deployment was removed, the pods were terminated, but the user credential in the account JWT was never revoked. This is the most common cause.
Developer credentials left after offboarding. Individual developers often have personal NATS credentials for local development or debugging. When they leave the team or organization, their credentials aren’t part of standard IT offboarding because NATS user management is typically handled by the platform team separately.
Credentials created for CI/CD or testing. Automated pipelines create user credentials for integration tests, load tests, or ephemeral environments. The pipeline runs once, the environment is destroyed, but the credentials remain in the account configuration.
Credential rotation created new users without removing old ones. Security policy required credential rotation. New user credentials were created, services were updated to use them, but the old credentials were never revoked. Both old and new credentials are valid; only the new ones are in use.
Batch or scheduled jobs that run infrequently. A user credential belongs to a job that runs weekly, monthly, or on-demand. Between runs, the user appears disconnected. This is a legitimate pattern, not a problem — but it requires context to distinguish from genuinely abandoned credentials.
In JWT-based deployments, list all users in an account:
nsc list users -a MY_ACCOUNTThen check which users have active connections:
curl -s http://localhost:8222/connz?auth=true | jq '.connections[] | {name: .name, account: .account, user: .authorized_user}'Users that appear in the nsc list output but not in connz are disconnected.
The server’s connection log shows recent disconnections and their reasons:
curl -s "http://localhost:8222/connz?state=closed&sort=stop&limit=50" | jq '.connections[] | {user: .authorized_user, stop: .stop, reason: .reason}'This shows recently closed connections. A user that disconnected hours ago might reconnect soon (legitimate). A user whose last connection was weeks ago is likely unused.
Combine with the account statistics to see the broader picture:
curl -s http://localhost:8222/accstatz | jq '.account_statz[] | select(.acc == "MY_ACCOUNT") | {conns: .conns, total_conns: .total_conns}'If the account’s total_conns (lifetime) is much higher than conns (current), many users have connected in the past but are no longer active.
Understanding the credential type informs the remediation approach:
nsc describe user -a MY_ACCOUNT -n SERVICE_USERCheck the issuer, expiration, and permissions. Users with no expiration and broad permissions are higher-risk candidates for cleanup.
For JWT-based deployments, revoke the user to immediately prevent any future connections:
nsc revocations add-user -a MY_ACCOUNT -n OLD_SERVICE_USERnsc push -ARevocation is immediate — the server rejects connections using the revoked credential on the next auth check. Existing connections (if any) are terminated.
When creating credentials for services or developers, set an expiration:
# Create a user credential that expires in 90 daysnsc add user -a MY_ACCOUNT -n DEVELOPER_USER --expiry 90dFor service credentials, align expiration with your deployment cycle:
1// Go - generate user JWT with expiration2import (3 "github.com/nats-io/jwt/v2"4 "github.com/nats-io/nkeys"5 "time"6)7
8claims := jwt.NewUserClaims(userPubKey)9claims.Expires = time.Now().Add(90 * 24 * time.Hour).Unix()10claims.Name = "order-service-prod"11claims.Sub = userPubKey12
13token, err := claims.Encode(accountSigningKey)1# Python - nats.py connection with credential rotation awareness2import nats3
4async def connect_with_rotation():5 nc = await nats.connect(6 "nats://server:4222",7 user_credentials="service-user.creds",8 error_cb=handle_error,9 reconnected_cb=handle_reconnect,10 )11 return nc12
13async def handle_error(e):14 if "authorization" in str(e).lower():15 # Credential may have expired or been revoked16 # Trigger credential refresh from secrets manager17 passTie user credentials to service deployments. Create credentials as part of your deployment pipeline and revoke them as part of your teardown pipeline. Kubernetes operators can manage this with init containers that request credentials and shutdown hooks that revoke them.
Maintain a credential inventory. Map every user credential to an owning service, team, and purpose. Review quarterly. Credentials that can’t be mapped to a running service should be revoked.
Use short-lived credentials for development. Developers should use credentials that expire in hours or days, not permanent credentials. This eliminates the offboarding problem entirely — forgotten credentials self-destruct.
Use Synadia Insights for continuous monitoring. Insights flags users with no active connections automatically, every collection cycle. Instead of quarterly manual audits comparing nsc list users against connz, disconnected users surface as findings in real time — across every account in the deployment.
No. A disconnected user might belong to a batch job that runs on a schedule, a disaster recovery service that only connects during failover, or a developer who is between work sessions. The check flags the condition so operators can make informed decisions. Context determines whether the disconnection is expected or indicates an unused credential that should be revoked.
A valid but unused credential is an unmonitored access vector. If compromised, an attacker can authenticate and access the account’s subjects with no baseline traffic to trigger anomaly alerts. The risk is proportional to the credential’s permissions — a user with publish.> on a sensitive account is higher risk than one with read-only access to a test subject.
An inactive account (OPT_IDLE_005) has zero connections AND zero throughput across the entire observation window — no users connected at all. Disconnected users is a finer-grained check: the account may have other active users, but specific user credentials within it have no connections. An account with 10 users where 8 are disconnected is active (2 users are connected) but has credential sprawl.
Revoke first. Revocation is immediate and reversible — you can remove the revocation later if the credential turns out to be needed. Deletion is permanent and requires re-creating the user from scratch if it’s needed again. Use revocation as a safe first step, then delete after a waiting period with no complaints.
For batch jobs or scheduled services, document the expected connection pattern in your credential inventory: “connects weekly on Sunday at 02:00 UTC.” Set the user JWT expiration to be slightly longer than the job interval (e.g., 8 days for a weekly job) and build credential renewal into the job’s startup logic. This ensures the credential self-expires if the job is retired, while staying valid for normal operation.
With 100+ always-on audit Checks from the NATS experts, Insights helps you find and fix problems before they become costly incidents.
No alert rules to write. No dashboards to maintain.
News and content from across the community