AI & Agents

NATS Message Size Limit: Configuring Max Payload and Handling Large Payloads

The default NATS message size limit is `1 MB` (`1,048,576` bytes), defined by the server `max_payload` setting across NATS Core and JetStream. While administrators can configure `max_payload` up to `64 MB`, maintainers recommend keeping payloads under `8 MB` to protect cluster throughput and avoid TCP buffer bloat. For larger payloads generated by AI agents, teams use JetStream Object Store chunking or external shared workspaces via the claim-check pattern.

Tom Langridge 20 min read Updated
Architectural diagram showing message payload offloading from NATS to external workspace storage

What Are the Default NATS Message Size Limits?

The NATS message size limit is determined by the max_payload server setting, which defaults to 1 MB (1,048,576 bytes) across NATS Core and JetStream to ensure sub-millisecond latency and prevent buffer bloat. Attempting to publish a message exceeding this limit immediately triggers a Maximum Payload Violation error from the server and closes the client connection. This ceiling is an intentional design choice rather than an arbitrary constraint. NATS was architected as a high-throughput, low-latency control plane and telemetry fabric, designed to process tens of millions of small messages per second with minimal memory allocations. Understanding why the default ceiling exists, how client libraries negotiate the payload boundary, and where JetStream stream-level policies apply is essential before modifying any server configuration, as documented in the official NATS documentation.

When a client establishes a TCP connection to a NATS server, the server emits an initial INFO protocol frame formatted as a JSON object. This frame informs the client of server capabilities and constraints, including the active max_payload value in bytes. Official client libraries in Go, Python, Node.js, Java, Rust, and C# read this value during the handshake and enforce it locally. If an application attempts to publish a message exceeding the advertised ceiling, the client library rejects the call locally before transmitting any bytes across the socket. If a raw client bypasses local validation and streams an oversized frame, the server protocol parser returns -ERR 'Maximum Payload Violation' and severs the TCP connection.

Messaging Surface Default Payload Limit Configurable Upper Bound Recommended Operational Range Enforcement Behavior
NATS Core Pub/Sub 1 MB (1,048,576 bytes) 64 MB 64 KB to 1 MB (maximum 8 MB) Server terminates connection with Maximum Payload Violation
NATS JetStream Stream Inherits server max_payload Server max_payload (up to 64 MB) 64 KB to 1 MB (maximum 8 MB) Stream rejects publish with error 10043 (exceeds max_msg_size)
NATS JetStream Object Store Unlimited overall object size Bounded by storage volume 128 KB chunk messages Automatically splits files into 128 KB chunks and reassembles on retrieval
External Workspace Storage Up to 100 GB on Enterprise 25 GB on Starter to 100 GB on Enterprise Any file exceeding 1 MB File preserved in workspace; agent passes lightweight claim check URI over NATS

While NATS Core subjects route messages ephemerally in memory, JetStream adds persistence, message deduplication, and ordered replay. However, JetStream does not alter the fundamental transport constraints of the underlying server. Every message written to a JetStream stream must still pass through the NATS protocol parser, meaning that the server-level max_payload setting serves as an unyielding ceiling across both systems.

Client Handshake and Protocol Enforcement

The client-side handshake is the primary guardrail against oversized messages. During connection setup, the server transmits its configuration dictionary:

{
  "server_id": "NDJ2M4X7Q5K9...",
  "server_name": "nats-core-east-01",
  "version": "2.10.20",
  "proto": 1,
  "max_payload": 1048576,
  "headers": true,
  "jetstream": true
}

Client drivers store this limit in memory. When your code invokes publish or request operations, the client driver inspects the byte length of the subject, headers, and body. If the combined message exceeds the limit, the driver raises a client exception immediately. This prevents unnecessary socket writes and preserves network bandwidth.

NATS Core Versus JetStream Message Ceilings

A common misconception is that enabling JetStream automatically allows publishing multi-megabyte files. JetStream operates within the same protocol envelope as NATS Core. In addition to the global server ceiling, JetStream streams support an optional stream-level parameter named max_msg_size.

The max_msg_size property defines the maximum byte size of a single message accepted by that specific stream. If a stream sets max_msg_size: 262144 (256 KB) while the server ceiling is 1 MB, messages between 256 KB and 1 MB pass through NATS Core subscribers on the subject, but JetStream rejects attempts to append the message to the stream with error code 10043 (message size exceeds max_msg_size). The stream limit can only narrow the boundary; it cannot exceed the server global ceiling.

How to Configure max_payload in NATS Server and JetStream

Increasing the NATS message size limit requires updating the server configuration file. The configuration directive is max_payload, which accepts either a raw integer representing bytes or a human-readable size string such as 2MB, 4MB, or 8MB.

In a production nats-server.conf file, define max_payload within the global configuration block. Whenever you increase max_payload, you must also verify that max_pending is configured with an equal or greater value. The max_pending setting controls the maximum number of pending bytes buffered for an outbound client connection before NATS disconnects the subscriber as a slow consumer.

# /etc/nats/nats-server.conf
port: 4222
http_port: 8222
max_payload: 8MB
max_pending: 64MB
jetstream {
  store_dir: "/data/nats/jetstream"
  max_memory_store: 2147483648
  max_file_store: 53687091200
}

You can apply the configuration file when launching the server executable directly from the terminal or inside a container definition:

# launch nats-server with the configuration file
nats-server -c /etc/nats/nats-server.conf

The NATS server validates configuration values during initialization. If max_payload exceeds max_pending, the server outputs a warning or refuses to start, depending on your NATS server release.

Server Configuration and Runtime Signal Reloads

The max_payload property is hot-reloadable. You do not need to restart the NATS daemon or disrupt active client sessions to apply a larger or smaller limit. Update your configuration file and issue a SIGHUP signal to the running process:

# reload nats-server configuration using the built-in signal command
nats-server --signal reload

Upon receiving SIGHUP, the server parses the updated file, logs the new configuration values to standard output, and advertises the new limit to subsequent client connections. Existing connected clients continue to operate under their initially negotiated handshake until they reconnect, at which point their client libraries discover and adopt the new ceiling.

Setting Stream-Level max_msg_size in JetStream

When configuring streams in JetStream, you can enforce stricter size caps on individual message streams to protect specific topics from payload creep. You can define max_msg_size using the NATS CLI:

# create a stream with a dedicated 2MB message ceiling
nats stream add AGENT_EVENTS   --subjects="agent.events.>"   --storage=file   --max-msg-size=2MB   --retention=limits

Setting max_msg_size on high-frequency event streams prevents individual agents or services from saturating the stream with oversized payloads while allowing other streams on the same cluster to accept standard messages.

Why Large Payloads Cause Buffer Bloat and Slow Consumers

Tutorials often demonstrate how to change max_payload to 16 MB or 32 MB without discussing the operational consequences. Bumping this value indiscriminately degrades NATS cluster stability. NATS achieves high throughput by utilizing internal memory pools (sync.Pool in Go) designed for predictable, small byte slices. Standard pooled buffers accommodate payloads up to 64 KB and 1 MB. When publishers transmit payloads such as 16 MB or 32 MB, these massive allocations bypass standard buffer pools and require direct heap allocations from the Go runtime.

When multiple producers publish large payloads concurrently, the Go memory allocator experiences severe heap churn, inducing garbage collection pauses that degrade cluster-wide throughput.

A second critical issue is client connection multiplexing. In NATS, a single TCP connection multiplexes many subject subscriptions. When an agent sends a 32 MB payload across the socket, that connection is monopolized for the duration of the transfer. Heartbeats, ping requests, consumer acknowledgments, and critical coordination events queue behind the large payload. If a heartbeat fails to clear before the ping deadline expires, the server assumes the client is unresponsive and severs the connection.

Slow consumer disconnects also multiply with payload size. When a producer broadcasts an 8 MB message to a subject with ten subscribers, the NATS server must write 80 MB across ten outbound socket buffers. If even one subscriber experiences temporary network congestion or CPU saturation, its outbound buffer fills immediately. Once the unread backlog reaches max_pending or the write deadline expires, NATS terminates the subscriber with a slow consumer error.

In clustered deployments and multi-region superclusters, large payloads must be forwarded across inter-cluster gateway routes. Replicating heavy messages across wide-area networks saturates interconnect bandwidth, delays Raft consensus groups in JetStream, and increases failover recovery times. Official NATS documentation notes that maintainers advise avoiding payload values above 8 MB even though the parameter allows configurations up to 64 MB. Exceeding 8 MB converts an agile message broker into a heavy blob transport, defeating the architecture of NATS.

Memory Pooling and Garbage Collection Overhead

The Go runtime memory allocator groups small allocations into pre-allocated memory arenas. Because standard NATS message handling cycles through pooled byte buffers, memory allocation per message approaches zero under normal workloads.

When a message exceeds standard slab boundaries, the allocator requests fresh spans from the operating system that must subsequently be reclaimed by the garbage collector. Under heavy traffic, heap allocations escalate rapidly, leading to CPU throttling and unpredictable delivery latency.

Multiplexing Contention and Socket Buffer Saturation

NATS multiplexes control frames, heartbeats, and data messages through the same TCP connection. When a client begins sending a multi-megabyte binary payload, all subsequent writes on that connection must wait in line.

If an orchestrator agent sends a task assignment followed by a cancellation signal, the cancellation signal cannot traverse the socket until the heavy payload transfer completes, introducing head-of-line blocking across distributed agent workflows.

How JetStream Object Store Slices Payloads into Chunks

When architectures require storing files within NATS without modifying server-level max_payload settings, JetStream Object Store provides a built-in solution. The Object Store is an abstraction layer built directly on top of standard JetStream streams that handles chunking and reassembly automatically.

When a client saves an object, the NATS client library splits the file into slices. By default, Object Store uses a chunk size of 128 KB (131,072 bytes). Each chunk is published as an individual message to a system subject following the pattern $O.<bucket>.C.>, where <bucket> is the name of your object store. Once all chunks are written, the client publishes a final metadata record to $O.<bucket>.M.> containing the object name, total byte count, chunk count, and SHA-256 digest.

You can manage object store buckets and inspect files using the NATS CLI:

# create an object store bucket with file storage backing
nats object add agent-artifacts --storage=file
# upload a research document or model artifact into the bucket
nats object put agent-artifacts research-summary.pdf
# inspect object metadata, chunk count, and SHA-256 digest
nats object info agent-artifacts research-summary.pdf
# download the assembled file back to disk
nats object get agent-artifacts research-summary.pdf -o output.pdf
Information for Object agent-artifacts / research-summary.pdf
Size: 14.8 MiB
Chunks: 119
Chunk Size: 128 KiB
Digest: SHA-256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Modified: 2026-10-13 04:12:08

While Object Store effectively bypasses the single-message limit without disrupting cluster memory pools, it introduces other operational tradeoffs. All object data still resides on NATS server storage volumes. Storing hundreds of gigabytes of agent outputs, raw datasets, and media files directly inside JetStream bloats server disks, inflates backup snapshot sizes, and complicates cluster migrations. Furthermore, JetStream Object Store does not provide document indexing, semantic search, version comparison tools, or browser-based access for human teammates.

How Object Store Slices Payloads into 128 KB Messages

Because Object Store messages are fixed at 128 KB chunks, they fit comfortably inside the default 1 MB ceiling. A 16 MB PDF is divided into 128 distinct messages. The NATS broker routes these chunks across standard JetStream storage pipelines without triggering high-order heap allocations.

When a consumer reads the object, the client driver streams the chunks sequentially, verifies the running SHA-256 digest against the metadata record, and exposes a readable stream to the application. If any chunk is corrupted or missing during transfer, the client driver catches the digest mismatch and raises an integrity error.

Storage Tradeoffs and Broker Disk Overhead

Treating NATS Object Store as a primary document repository creates operational friction at scale. JetStream was designed for event streaming and short-to-medium term data retention. Storing heavy, long-lived artifacts on broker disks forces engineering teams to over-provision high-performance storage on their messaging nodes.

When cluster nodes rebalance, Raft groups must replicate the underlying streams across replacement nodes. Replicating gigabytes of chunked binary files over internal networks slows leader recovery. For teams producing heavy deliverables, decoupling messaging from persistent storage is the cleaner architecture.

Fastio features

Store and coordinate agent files outside your message bus

Give your AI agents a persistent, versioned workspace with an MCP-ready endpoint for large file reads and writes. Monthly plans start with a 14-day free trial, credit card required.

How to Offload Agent Payloads with the Claim Check Pattern

The standard architectural pattern for handling large payloads in distributed systems is the Claim Check pattern. Instead of transmitting heavy payloads through the messaging broker, the producer stores the asset in a dedicated, external storage layer. The producer then publishes a tiny, structured event message (typically 500 bytes to 2 KB) over NATS. This event message contains the metadata, hash, and access reference: the claim check.

Downstream subscribers receive the event notification over NATS in microseconds. Because the message is lightweight, it consumes minimal broker memory, avoids slow consumer disconnects, and does not block other subjects on the connection. Consumers that only need to track progress read the metadata and acknowledge the message immediately. Consumers that require the underlying content retrieve the file from external storage on demand.

Modern AI agent pipelines benefit directly from this architecture. Autonomous agents and multi-agent frameworks produce substantial artifacts: multi-page PDF research reports, tabular analysis datasets, high-resolution visual outputs, and complete software repositories. Passing multi-megabyte context dumps or generated files through NATS threatens bus performance. Furthermore, downstream agents and human collaborators require more than raw byte storage: they need structured document indexing, semantic search, version history, and human-in-the-loop review.

In an intelligent workspace platform such as Fastio, files do not sit in passive storage. Teams use Fastio workspaces to organize shared documents with per-file version history. When Intelligence Mode is enabled on a workspace, files are automatically indexed for hybrid search, combining full-text keyword retrieval, semantic vector search, and metadata value filtering. Autonomous agents interact directly with Fastio workspaces through a consolidated Model Context Protocol (MCP) toolset, providing dedicated storage for AI agents.

AI agents connect to Fastio using Streamable HTTP at the appropriate endpoint: Claude applications and general client runtimes connect at https://mcp.fast.io/mcp/tools, while coding agents connect at https://mcp.fast.io/mcp/code. Setup instructions for teams are documented in the MCP documentation, with detailed tool specifications in the MCP skill guide. In headless agent environments, authentication is provided by sending Authorization: Bearer <api key> on the connection. On /mcp/tools, write operations use the dedicated _manage toolset (such as storage_manage and upload_manage), while read operations use storage.

When an agent produces a heavy dataset or document (such as a 45 MB dossier), it writes the file to the shared workspace using MCP. Fastio saves the file, updates version history, and generates search embeddings. The agent then publishes a compact claim check over NATS containing the workspace identifier and file reference. Downstream agents and human colleagues can immediately inspect the file, ask questions against it using natural language, or compare revisions. For structured documents such as contracts, invoices, and forms, Metadata Views extract typed columns automatically without custom OCR rules.

An agent can build an organization and transfer ownership to a human team lead with a claim link. Monthly plans start with a 14-day free trial that requires a credit card. Paid subscription tiers on Fastio pricing include Starter at $9.99/mo (250 GB storage, 3 seats, 5 workspaces, 100,000 credits a month), Business at $49.99/mo (5 TB storage, 10 seats, 50 workspaces, 600,000 credits a month), and Enterprise at $199.99/mo (25 TB storage, 30 seats included, 200 workspaces, 3,000,000 credits a month). Maximum upload sizes are 25 GB on Starter, 50 GB on Business, and 100 GB on Enterprise. Credits meter AI operations, and overage is billed at $10 per 100,000 credits.

Implementing the Claim Check Pattern in Python

The following Python example demonstrates an autonomous agent publishing a lightweight claim check over NATS after offloading a research artifact to workspace storage:

import asyncio
import json
import hashlib
from nats.aio.client import Client as NATS

async def run_agent_publisher():
    nc = NATS()
    await nc.connect("nats://localhost:4222")
    # in production, the agent saves the heavy artifact to Fastio workspace
    # storage via MCP tools or API. The file receives a persistent URI.
    artifact_path = "workspace://corp-research/briefs/q3-market-analysis.pdf"
    artifact_bytes = b"Sample multi-megabyte research dossier content..."
    file_sha256 = hashlib.sha256(artifact_bytes).hexdigest()
    file_size_bytes = 28456192
    # build a compact claim check event message (under 500 bytes)
    claim_check = {
        "event_id": "evt_88391024",
        "topic": "market_analysis",
        "status": "completed",
        "file_uri": artifact_path,
        "sha256": file_sha256,
        "byte_size": file_size_bytes,
        "mime_type": "application/pdf",
        "workspace_id": "ws_7109a"
    }
    # publish the claim check over NATS
    payload = json.dumps(claim_check).encode("utf-8")
    await nc.publish("agent.research.completed", payload)
    print(f"Published claim check ({len(payload)} bytes) for {file_size_bytes} byte file")
    await nc.drain()

if __name__ == "__main__":
    asyncio.run(run_agent_publisher())

Subscribing services and agents listen on agent.research.completed, extract the URI, and query the workspace through MCP or the Fastio API only when full document processing is required.

Collaborative Notes and Version History Across Multi-Agent Runs

In complex multi-agent systems where several agents contribute to one project, concurrent file operations must remain coordinated. Fastio maintains complete per-file version history, recording every file edit, move, and replacement. When an editor agent refines a document written by a research agent, previous versions remain intact and restorable.

Agents and human colleagues can also collaborate directly through Notes kept beside workspace files. Notes feature version-checked saves rather than real-time co-editing, allowing agents to record research citations, structured findings, and project notes without overwriting active human edits. Every storage change and permission update is logged in the organization detailed activity log, ensuring transparent oversight of all automated workflows.

When to Scale max_payload Versus External Workspace Storage

Choosing between increasing max_payload, utilizing JetStream Object Store, and deploying external workspace storage depends on payload size, consumer distribution, and collaboration requirements.

Use a modest increase in NATS max_payload (for example, 2 MB to 4 MB) only when message sizes are consistently slightly above the default, producers and consumers are homogeneous internal microservices, and you have configured max_pending and network buffers accordingly. Keep max_payload below 8 MB at all times to protect Go memory allocation pools and prevent slow consumer drops.

Use NATS JetStream Object Store when transferring ephemeral binary assets between 1 MB and 10 MB where consumers are purely automated NATS clients and files have short retention windows. Object Store handles chunking gracefully without altering the server single-message limit.

Use external workspace storage via the Claim Check pattern for files exceeding 1 MB that require persistence, human inspection, cross-agent collaboration, or intelligent search. Offloading documents, datasets, and deliverables to Fastio workspaces keeps the NATS control plane running at sub-millisecond latency while providing agents with semantic indexing, version tracking, and client-ready share links.

Architectural Dimension High max_payload (4MB to 8MB) JetStream Object Store External Workspace (Fastio)
Optimal Payload Size 1 MB to 4 MB 1 MB to 10 MB 1 MB to 100 GB (Enterprise)
NATS Server Heap Impact High risk of GC latency spikes Low (messages fixed at 128 KB) Zero heap impact on NATS
Broker Storage Demand In-memory / ephemeral Consumes JetStream broker disk Externalized off NATS broker
Search and Retrieval None (raw byte stream) None (subject-based retrieval) Semantic vector, full-text, and metadata search
Human Collaboration None (requires NATS client) None (CLI / code only) Web UI, branded shares, Notes, version history
Multi-Agent Handoff Ephemeral socket messaging Subject namespace coordination Scoped workspaces, ownership transfer, MCP tools

Monitoring NATS Buffer Health and Slow Consumers

When tuning NATS message limits, continuous monitoring is critical. NATS exposes live operational metrics via its HTTP monitoring interface (default port 8222). Query the /varz endpoint to inspect connection health, memory allocations, and buffer utilization:

# query NATS server monitoring endpoint for buffer health
curl -s http://localhost:8222/varz | jq '{
  connections: .connections,
  in_msgs: .in_msgs,
  out_msgs: .out_msgs,
  in_bytes: .in_bytes,
  out_bytes: .out_bytes,
  slow_consumers: .slow_consumers,
  mem: .mem
}'

A non-zero or increasing slow_consumers count is an immediate indicator that subscriber outbound buffers are saturating. If you have recently increased max_payload, an uptick in slow consumers confirms that large messages are overwhelming client sockets. When this occurs, revert max_payload to 1 MB and transition oversized data to workspace storage using the claim-check pattern.

Client-Side Size Validation and Routing Guards

Implement application-level routing guards within your agent frameworks to evaluate payload length prior to publishing.

If the payload is under 512 KB, publish directly to NATS Core or JetStream. If the payload exceeds 512 KB, automatically route the asset to Fastio workspace storage over MCP, obtain the claim check reference, and publish the lightweight reference event. This hybrid routing guarantees that NATS remains fast and responsive while providing unbounded capacity for rich agent deliverables.

Sources

References used to verify factual claims in this guide.

  1. Official NATS documentation states that the server defaults to a 1 MB maximum payload across NATS Core and JetStream, and while configuration supports up to 64 MB, maintainers recommend that administrators avoid values over 8 MB.

Frequently Asked Questions

What is the maximum message size in NATS?

The default maximum message size in NATS is `1 MB` (`1,048,576` bytes) across NATS Core and JetStream, defined by the server `max_payload` configuration setting. While administrators can configure `max_payload` up to `64 MB`, NATS maintainers recommend keeping payloads under `8 MB` to protect cluster stability, avoid Go runtime memory fragmentation, and prevent slow consumer disconnects.

How do I increase the max payload in NATS server?

To increase the maximum payload, add `max_payload: 8MB` to your `nats-server.conf` configuration file and start the server with `nats-server -c /etc/nats/nats-server.conf`. Ensure that `max_pending` is configured equal to or greater than `max_payload`. You can apply this change to a running server without downtime by triggering a configuration reload using `nats-server --signal reload` or sending a SIGHUP signal.

How should AI agents send large files over NATS?

AI agents should handle large files using the claim-check pattern rather than pushing heavy payloads across the NATS message bus. The agent uploads the file to dedicated external workspace storage (such as a Fastio workspace using Model Context Protocol), then publishes a lightweight JSON event (under `1 KB`) over NATS containing the file reference, hash, and metadata. Downstream consumers retrieve or query the file from storage only when needed.

What error does NATS return when a message exceeds max_payload?

When a client attempts to publish a payload larger than the server `max_payload` setting, the NATS server issues a `-ERR 'Maximum Payload Violation'` protocol error and terminates the client TCP socket. In JetStream, publishing to a stream with an active `max_msg_size` limit that is smaller than the payload returns JetStream error 10043 (`message size exceeds max_msg_size`).

What is the difference between max_payload and JetStream max_msg_size?

The `max_payload` setting is a server-wide or account-wide limit that governs the maximum byte size of any protocol message entering the NATS daemon. In contrast, `max_msg_size` is a stream-specific setting in JetStream that restricts the largest message accepted by that particular stream. A stream `max_msg_size` cannot exceed the server global `max_payload`.

Does NATS JetStream Object Store require increasing max_payload?

No. NATS JetStream Object Store does not require increasing `max_payload`. It automatically splits large files into small `128 KB` chunks upon upload and publishes each chunk as an individual JetStream message. When an application retrieves the object, the client library reads the sequential chunks and reassembles the complete file, bypassing the server single-message payload ceiling while keeping individual message sizes well within default limits.

Can max_payload be updated at runtime without restarting NATS?

Yes. The `max_payload` parameter is hot-reloadable. After updating `max_payload` in `nats-server.conf`, send a SIGHUP signal to the server process using `kill -HUP <PID>` or execute `nats-server --signal reload`. The server re-reads the configuration, logs the change, and advertises the new limit to subsequent client connections through the initial `INFO` protocol frame without dropping active connections.

Related Resources

Fastio features

Store and coordinate agent files outside your message bus

Give your AI agents a persistent, versioned workspace with an MCP-ready endpoint for large file reads and writes. Monthly plans start with a 14-day free trial, credit card required.