Checks/OPT_IDLE_006

NATS Disconnected Users: What It Means and How to Fix It

Severity
Info
Category
Consistency
Applies to
Idle Resources
Check ID
OPT_IDLE_006
Detection threshold
No active connections at current epoch (excludes system)

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.

Why this matters

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.

Common causes

  • 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.

How to diagnose

List users and check for active connections

In JWT-based deployments, list all users in an account:

Terminal window
nsc list users -a MY_ACCOUNT

Then check which users have active connections:

Terminal window
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.

Check connection history

The server’s connection log shows recent disconnections and their reasons:

Terminal window
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.

Cross-reference with account activity

Combine with the account statistics to see the broader picture:

Terminal window
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.

Identify the credential type

Understanding the credential type informs the remediation approach:

Terminal window
nsc describe user -a MY_ACCOUNT -n SERVICE_USER

Check the issuer, expiration, and permissions. Users with no expiration and broad permissions are higher-risk candidates for cleanup.

How to fix it

Immediate: revoke credentials you know are unused

For JWT-based deployments, revoke the user to immediately prevent any future connections:

Terminal window
nsc revocations add-user -a MY_ACCOUNT -n OLD_SERVICE_USER
nsc push -A

Revocation is immediate — the server rejects connections using the revoked credential on the next auth check. Existing connections (if any) are terminated.

Short-term: set expiration on user JWTs

When creating credentials for services or developers, set an expiration:

Terminal window
# Create a user credential that expires in 90 days
nsc add user -a MY_ACCOUNT -n DEVELOPER_USER --expiry 90d

For service credentials, align expiration with your deployment cycle:

1
// Go - generate user JWT with expiration
2
import (
3
"github.com/nats-io/jwt/v2"
4
"github.com/nats-io/nkeys"
5
"time"
6
)
7
8
claims := jwt.NewUserClaims(userPubKey)
9
claims.Expires = time.Now().Add(90 * 24 * time.Hour).Unix()
10
claims.Name = "order-service-prod"
11
claims.Sub = userPubKey
12
13
token, err := claims.Encode(accountSigningKey)
1
# Python - nats.py connection with credential rotation awareness
2
import nats
3
4
async 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 nc
12
13
async def handle_error(e):
14
if "authorization" in str(e).lower():
15
# Credential may have expired or been revoked
16
# Trigger credential refresh from secrets manager
17
pass

Long-term: implement credential lifecycle management

Tie 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.

Frequently asked questions

Is a disconnected user always a problem?

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.

What’s the security risk of leaving disconnected users in place?

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.

How is this different from an inactive account?

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.

Should I revoke or delete disconnected users?

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.

How do I handle credentials for infrequently-running services?

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.

Proactive monitoring for NATS disconnected users with Synadia Insights

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.

Start a 14-day Insights trial
Cancel