Checks/OPT_IDLE_005

NATS Inactive Account: What It Means and How to Fix It

Severity
Info
Category
Health
Applies to
Idle Resources
Check ID
OPT_IDLE_005
Detection threshold
Zero connections and zero throughput for the configured inactivity threshold — default 24 h (excludes system accounts)

An inactive account is a NATS account with zero client connections and zero throughput — inbound or outbound — for the configured inactivity threshold (default 24 hours). The account exists in the server’s configuration or resolver, has allocated resources and limits, but nothing is using it. System accounts are excluded from this check.

Why this matters

Multi-tenant NATS deployments use accounts to isolate workloads, enforce resource limits, and control subject namespace access. Each account is a security and resource boundary. When accounts go inactive, they don’t consume significant runtime resources — the server doesn’t allocate memory for connections that don’t exist. The costs are more subtle.

Inactive accounts expand the security surface area. Every account has associated credentials — user JWTs, NKeys, or static tokens. Credentials for inactive accounts are still valid unless explicitly revoked. An attacker who obtains credentials for a “forgotten” account gets access to a NATS deployment where nobody is watching for anomalous connections. The account might have imports or exports wired to other accounts, providing a lateral movement path that’s invisible because nobody monitors the inactive account’s traffic.

The operational cost compounds over time. Account configurations accumulate in server configs or the NATS resolver. Operators reviewing accstatz output or Insights dashboards see accounts they can’t map to real teams or services. During incidents, they waste time investigating whether an inactive account is related to the problem. During capacity planning, ghost accounts with reserved JetStream limits inflate the apparent resource commitment without delivering any value. In JWT-based deployments, operator signing overhead grows linearly with account count — every account needs credential management, rotation schedules, and export/import reviews regardless of whether anyone uses it.

Common causes

  • Team or project was retired. The team that owned the account disbanded, or the project completed. The services were shut down, but the account was never removed from the NATS configuration. This is the most common cause in organizations that don’t tie account lifecycle to project lifecycle.

  • Test or staging account left behind. An account was created for integration testing, load testing, or a proof of concept. The test concluded, but the account persists in the server configuration or resolver.

  • Migration to a different account completed. A workload was migrated from one account to another — perhaps for organizational restructuring, updated permissions, or a naming convention change. The old account was never cleaned up.

  • Account pre-provisioned for future use. An account was created in advance for a planned service or team that hasn’t deployed yet — or never will. Without an expiration mechanism, pre-provisioned accounts sit indefinitely.

  • Credentials rotated to a new account. Security policy required credential rotation, and the team created a new account with fresh credentials instead of rotating within the existing account. The old account became a ghost.

How to diagnose

List accounts and check activity

Query the server’s account statistics endpoint:

Terminal window
curl -s http://localhost:8222/accstatz | jq '.account_statz[] | select(.conns == 0) | {acc: .acc, conns: .conns, sent: .sent, received: .received}'

This shows accounts with zero active connections. Cross-reference with message counts — an account with zero connections AND zero sent/received messages across your observation window is inactive.

Check for JetStream assets in the account

An account might have no connections but still own streams or consumers:

Terminal window
nats stream ls # account is selected via NATS context/credentials
nats consumer report # account is selected via NATS context/credentials

Streams with data may need to be preserved or migrated before removing the account. An account with active JetStream assets but no connections is a different kind of problem — the data exists but nobody is consuming it.

Review account exports and imports

Check whether the inactive account has exports that other accounts depend on:

Terminal window
nats server account info INACTIVE_ACCOUNT

If the account exports services or streams that active accounts import, removing the account will break those imports. This is a critical dependency check before cleanup.

Verify in JWT-based deployments

For nsc-managed deployments, list all accounts and cross-reference with activity:

Terminal window
nsc list accounts

Compare the account list with the active accounts from accstatz. Accounts that appear in nsc but not in accstatz (or with zero activity) are candidates for review.

How to fix it

Immediate: confirm the account is truly unused

Before removing any account, verify:

  1. No JetStream streams or consumers exist in the account
  2. No other accounts import from this account’s exports
  3. No credentials for this account are in active use (check secrets management systems)
  4. The owning team confirms the account is no longer needed
Terminal window
# Check for any remaining assets
nats stream ls # account is selected via NATS context/credentials
nats consumer report # account is selected via NATS context/credentials
nats server account info INACTIVE_ACCOUNT

Short-term: remove or disable the account

For config-file accounts:

Remove the account block from the server configuration and reload:

Terminal window
nats-server --signal reload

For JWT-based accounts (nsc):

Revoke the account to immediately prevent any connections, then delete it:

Terminal window
# Revoke the account (immediately prevents connections)
nsc revocations add-account -n INACTIVE_ACCOUNT
# Delete the account from the operator
nsc delete account -n INACTIVE_ACCOUNT
# Push the updated configuration
nsc push -A

Migrate JetStream data if needed:

If the account owns streams with data that needs preservation, export the data first:

1
// Go - nats.go
2
// Export stream data before account removal
3
nc, _ := nats.Connect(url, nats.UserCredentials("inactive-account-creds.jwt"))
4
js, _ := nc.JetStream()
5
6
sub, _ := js.SubscribeSync("data.>",
7
nats.DeliverAll(),
8
nats.AckExplicit(),
9
)
10
11
for {
12
msg, err := sub.NextMsg(5 * time.Second)
13
if err != nil {
14
break // no more messages
15
}
16
// Write to new location, backup, etc.
17
msg.Ack()
18
}
1
# Python - nats.py
2
import nats
3
4
nc = await nats.connect(user_credentials="inactive-account-creds.jwt")
5
js = nc.jetstream()
6
7
sub = await js.subscribe("data.>", deliver_policy=nats.js.api.DeliverPolicy.ALL)
8
9
while True:
10
try:
11
msg = await sub.next_msg(timeout=5)
12
# Write to new location, backup, etc.
13
await msg.ack()
14
except nats.errors.TimeoutError:
15
break

Long-term: implement account lifecycle governance

Document account ownership. Maintain a registry mapping each account to an owning team, project, and purpose. When a project ends or a team reorganizes, the registry flags accounts for review.

Set account expiration in JWTs. When creating accounts for time-bounded purposes (testing, migrations, short-lived projects), set an expiration on the account JWT:

Terminal window
nsc add account -n MIGRATION-2024Q3 --expiry 90d

Expired account JWTs are automatically rejected by the server, preventing ghost accounts from persisting indefinitely.

Use Synadia Insights for continuous monitoring. Insights automatically flags accounts with zero connections and zero throughput across the observation window. Instead of periodic manual audits, inactive accounts surface as findings every collection cycle — before they accumulate into a cleanup project.

Frequently asked questions

Does an inactive account waste server resources?

Minimal runtime resources — the server doesn’t allocate connection buffers or subscription tracking for accounts with no connections. The costs are in JetStream reservations (if the account has reserved memory or storage limits), configuration complexity, credential management overhead, and security surface area. The more accounts you have, the more operational burden each configuration change, audit, or incident investigation carries.

Should I delete inactive accounts or just revoke them?

Revocation is safer as a first step. Revoking an account prevents any connections using its credentials without deleting the account’s configuration, JetStream assets, or import/export relationships. If nobody complains after a reasonable period (days to weeks), proceed with deletion. This two-phase approach catches cases where an account appears inactive but is actually used by a batch job that runs infrequently.

Can an inactive account still have JetStream streams with data?

Yes. Streams persist independently of client connections. An account with no active connections can still own streams with gigabytes of data. Deleting the account without addressing its streams may orphan that data or cause errors depending on your server version. Always check `nats stream ls # account is selected via NATS context/credentials before removing an account.

How do I prevent accounts from becoming inactive in the first place?

Tie account creation to your service deployment pipeline. Accounts should be created when services deploy and flagged for review when services are decommissioned. For JWT-based deployments, set expiration dates on accounts created for temporary purposes. For config-file deployments, include account reviews in your regular infrastructure audits.

Proactive monitoring for NATS inactive account 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