nats-io/nats-server · error

config reload not supported for jetstream dynamic max memory

Error message

config reload not supported for jetstream dynamic max memory and store

What it means

When `jetstream.max_memory` or `jetstream.max_store` is unset (-1 / absent) in the old config, JetStream uses dynamic limits. The reload path forbids transitions from unset to explicitly set limits at runtime, because the dynamic account limit propagation cannot be safely re-derived mid-flight. The reload is rejected with this error.

Source

Thrown at server/reload.go:1860

						jsLimitsUpdate = &jetStreamLimitsOption{}
						diffOpts = append(diffOpts, jsLimitsUpdate)
					}
					if optName == "jetstreammaxmemory" {
						jsLimitsUpdate.newMaxMemory = new
					} else {
						jsLimitsUpdate.newMaxStore = new
					}
				case fromSet && toUnset:
					// Limits changed but it may mean that JS is being disabled,
					// keep track of the change and error in case it is not.
					if optName == "jetstreammaxmemory" {
						jsMemLimitsChanged = true
					} else {
						jsFileLimitsChanged = true
					}
				case fromUnset && toSet:
					// Prevent changing from dynamic max memory / file at runtime.
					return nil, fmt.Errorf("config reload not supported for jetstream dynamic max memory and store")
				default:
					return nil, fmt.Errorf("config reload not supported for decreasing jetstream max memory and store")
				}
			}
		case "jetstreammetacompact", "jetstreammetacompactsize", "jetstreammetacompactsync":
			// Allowed at runtime but monitorCluster looks at s.opts directly, so no further work needed here.
		case "jetstreamconcurrentios":
			// Not reloadable at runtime; preserve the current value while JetStream is disabled,
			// e.g. the entire jetstream{} block was deleted.
			if newOpts.JetStream {
				return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v",
					field.Name, oldValue, newValue)
			} else {
				newOpts.JetStreamConcurrentIOs = oldValue.(int)
			}
		case "websocket":
			// Similar to gateways
			tmpOld := oldValue.(WebsocketOpts)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Restart the server to apply explicit max_memory/max_store for the first time
  2. Keep limits unset (-1) if reload continuity is required and manage caps at the account level where possible
  3. Change existing explicit limits (set→set) which may be allowed if not decreasing — avoid unset→set transitions

Example fix

// before
jetstream { max_memory: 1GB }  # was unset, + reload signal
// after
server # systemctl restart nats-server  (first-time explicit limits need restart)
Defensive patterns

Strategy: validation

Validate before calling

// Block unset→set limit transitions before reload:
func dynamicToStaticLimits(old, new *Options) bool {
	const unset = int64(-1)
	if old.JetStream && new.JetStream {
		if (old.MaxMemory == unset || old.MaxMemoryMax == unset) && new.MaxMemory > 0 { return true }
		if (old.MaxStore == unset || old.MaxStoreMax == unset) && new.MaxStore > 0 { return true }
	}
	return false
}

Prevention

When it happens

Trigger: Signaling reload after changing `jetstream.max_memory` or `jetstream.max_store` from absent/-1 to a concrete byte value (e.g. 1GB) while JetStream is enabled.

Common situations: Operators first running without limits then deciding to cap usage and attempting to apply it via reload; config automation that adds explicit limits to a config that previously omitted them; aligning configs across a cluster where some nodes had limits and others didn't.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/55f16c3f36b5e590. Report an issue: GitHub.