Checks/OPT_SYS_008

NATS Unlimited JetStream Account: What It Means and How to Fix It

Severity
Info
Category
Consistency
Applies to
System Improvement
Check ID
OPT_SYS_008
Detection threshold
JetStream memory and disk storage both set to -1 (unlimited) on a non-system account

An unlimited JetStream account is a non-system NATS account that has JetStream enabled but no memory or disk storage limits configured. Without limits, a single account can consume the entire server’s JetStream reservation, starving every other account of storage resources.

Why this matters

JetStream accounts without storage limits violate the most basic principle of multi-tenant resource management: isolation. When any account can allocate unlimited streams with unlimited storage, there is no mechanism preventing one team’s backfill job, misconfigured retention policy, or runaway producer from consuming every byte of disk or memory the server has reserved for JetStream. The failure mode isn’t theoretical — it’s a matter of when, not if.

The blast radius extends beyond the offending account. When one account exhausts the server’s JetStream file storage, every other account’s stream creation fails. Existing streams across all accounts start rejecting publishes once storage is full. In clustered deployments, the exhaustion propagates to every server that hosts replicas of the unlimited account’s streams. A single account’s unbounded growth can trigger the Storage Utilization Critical check (JETSTREAM_007) cluster-wide, turning an optimization issue into a critical outage.

Even in single-tenant deployments, unlimited accounts create operational blind spots. Without explicit limits, there is no early warning when consumption approaches capacity. The JetStream Resource Pressure check (SERVER_005) fires at 90% of the server reservation, but if the account limit is “everything the server has,” that warning comes too late to avoid impact. Setting explicit account limits creates a buffer between what an account is allowed to use and what the server can provide — the difference between a graceful rejection and a cluster-wide storage crisis.

Common causes

  • Default account configuration. New accounts created without explicitly setting JetStream limits default to unlimited. This is common in development environments that get promoted to production without a configuration review.

  • Single-tenant assumption. Operators running a single application on NATS often skip account limits because “there’s only one tenant.” But applications grow, teams add workloads, and the single-tenant assumption breaks silently when a second team starts creating streams.

  • Migration from core NATS. Teams migrating from core NATS (publish/subscribe only) to JetStream may enable JetStream on existing accounts without adding storage limits. The account was fine without limits when it had no persistent state — now it has unbounded storage authority.

  • Operator JWT without JetStream claims. When using the NATS JWT-based auth system, the account JWT must include explicit JetStream limits. If the operator issues an account JWT with JetStream enabled but omits the mem_storage and disk_storage claims, the account gets unlimited access.

  • Copy-paste from examples. Many NATS configuration examples and tutorials use -1 (unlimited) for JetStream limits to simplify the getting-started experience. Production deployments that copy these values inherit the risk.

How to diagnose

List accounts and their JetStream limits

Use the nats CLI to inspect account information:

Terminal window
nats account info

Look for the JetStream section. If Memory and Storage limits show -1 or unlimited, the account has no resource cap.

Audit all accounts across the deployment

To find all unlimited accounts, query the server’s account information endpoint:

Terminal window
curl -s http://localhost:8222/accountz | jq '.accounts[] | select(.jetstream == true) | {name: .account_name, mem_limit: .memory, store_limit: .storage}'

Any account with mem_limit or store_limit of -1 is unlimited.

Check account limits with NSC (JWT deployments)

For deployments using the NATS JWT auth system, inspect account claims:

Terminal window
nsc describe account <account_name>

Look for the JetStream section — Max Mem Storage and Max Disk Storage of -1 confirm the account is unlimited.

Review current usage against capacity

Even if an unlimited account isn’t causing problems today, check how much headroom remains:

Terminal window
nats server report jetstream

Compare each server’s used storage against its total JetStream reservation. An unlimited account currently using 80% of available storage is one traffic spike away from exhaustion.

How to fix it

Immediate: assess current exposure

Determine what the unlimited account is actually using. Before setting limits, understand current consumption so you don’t set limits below current usage (which would prevent new stream creation):

Terminal window
nats account info --json | jq '{mem_used: .memory, store_used: .storage, streams: .streams, consumers: .consumers}'

Calculate appropriate limits. A good starting point: set the account limit to 150% of current usage, or divide the total server reservation proportionally across accounts. Leave at least 20% of the server’s total JetStream reservation unallocated as headroom.

Short-term: set account limits

For static configuration (accounts block):

1
accounts {
2
ORDERS {
3
jetstream {
4
max_mem: 512M
5
max_file: 50G
6
max_streams: 25
7
max_consumers: 100
8
}
9
users = [{user: "order-svc", password: "$ORDERS_PASS"}]
10
}
11
}

For JWT-based deployments using NSC:

Terminal window
nsc edit account ORDERS \
--js-mem-storage 512M \
--js-disk-storage 50G \
--js-streams 25 \
--js-consumer 100

Push the updated account JWT:

Terminal window
nsc push -a ORDERS

Programmatic account limit configuration in Go:

1
// Go — creating a stream that respects account limits
2
js, _ := nc.JetStream()
3
4
// The server enforces account limits automatically.
5
// This create will fail if it would exceed the account's max_file or max_streams.
6
_, err := js.AddStream(&nats.StreamConfig{
7
Name: "ORDERS",
8
Subjects: []string{"ORDERS.>"},
9
MaxBytes: 10 * 1024 * 1024 * 1024, // 10GB per-stream limit
10
Replicas: 3,
11
})
12
if err != nil {
13
// nats.ErrJetStreamResourcesExceeded if account limit hit
14
log.Fatalf("stream create failed: %v", err)
15
}
1
# Python — handling account limit errors
2
import nats
3
from nats.js.errors import BadRequestError
4
5
nc = await nats.connect()
6
js = nc.jetstream()
7
8
try:
9
await js.add_stream(name="ORDERS", subjects=["ORDERS.>"],
10
max_bytes=10 * 1024**3)
11
except BadRequestError as e:
12
# Server rejects if account storage limit exceeded
13
print(f"Account limit hit: {e}")

Long-term: implement a resource governance policy

Establish account provisioning standards. Every new account should have JetStream limits set at creation time. Document the expected limits for each workload class (e.g., “microservice accounts get 1GB mem / 50GB disk / 10 streams / 50 consumers”).

Set stream-level limits within accounts. Account limits cap total usage, but individual streams within the account should also have retention limits (max_bytes, max_msgs, max_age). This is flagged separately by the Streams Without Limits check (OPT_SYS_001). Defense in depth: account limits prevent cross-tenant impact, stream limits prevent intra-account waste.

Monitor account utilization. Set up alerts when account usage approaches limits so you can proactively adjust before applications start getting rejected.

Synadia Insights evaluates this automatically across all accounts, flagging unlimited accounts as optimization opportunities and near-limit accounts as operational warnings (ACCOUNTS_001).

Frequently asked questions

Is it safe to set limits on an account that currently has unlimited JetStream?

Yes, as long as you set the limits above the account’s current usage. If you set a storage limit below what the account is already consuming, existing streams continue to function, but the account cannot create new streams or expand existing ones until usage drops below the limit. Check current usage with nats account info before setting limits.

What happens when an account hits its JetStream storage limit?

The server rejects new stream creation and publish operations that would exceed the limit. Existing streams continue to serve reads and consumer operations. Streams with retention policies (max_age, max_bytes) will continue to expire old messages, which frees storage and allows new publishes to resume. The account receives a specific error code indicating resource exhaustion.

Do system accounts need JetStream limits?

No. The NATS system account ($SYS) is excluded from this check intentionally. The system account is used for internal server coordination and monitoring — its JetStream usage is controlled by the server itself, not by external workloads. Setting limits on the system account can interfere with server operations.

How should I divide JetStream storage across multiple accounts?

Start by assigning each account limits based on its expected workload, not by dividing total capacity equally. A stream processing pipeline that ingests 1TB/day needs more storage than an account running lightweight request-reply. Over-provision slightly (sum of all account limits can exceed server capacity by 20-30%) since not all accounts peak simultaneously — but monitor utilization to validate this assumption.

What is the difference between account JetStream limits and server JetStream reservation?

Server JetStream reservation (max_file_store, max_mem_store in the server config) sets the total capacity the server makes available for JetStream. Account limits divide that capacity among accounts. Think of the server reservation as the total pie and account limits as slices. Without account limits, any account can eat the entire pie.

Proactive monitoring for NATS unlimited jetstream 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