NATS 2.11 introduced message tracing—a built-in way to see exactly where your messages go. No external tools, no code instrumentation. Just run a command and watch your message flow through clusters, gateways, leaf nodes, and JetStream.
If you’ve ever needed to debug your system or just wanted to peek under the hood, this one’s for you.
Watch the video overview: Message Tracing in NATS
In a distributed NATS deployment with multiple clusters, leaf nodes, and gateways, a single message might hop through several servers before reaching its destination. When something goes wrong—a message isn’t delivered, latency spikes, or routing behaves unexpectedly—you need visibility into what’s actually happening.
Traditional approaches require external tracing infrastructure or manual instrumentation throughout your codebase. NATS takes a different approach: tracing is built directly into the server.
The nats trace command sends a message through your system and shows you exactly where it goes:
nats trace demo.subject "hello world"When you run this, NATS:
Each server your message touches sends back a trace event. These events are aggregated and displayed as a visual tree showing every hop, connection type, and destination.
When you run the trace command, three headers are automatically added to your message:
| Header | Purpose |
|---|---|
Nats-Trace-Dest | The inbox subject where trace events are sent |
Nats-Trace-Only | Enables dry-run mode—message is traced but not delivered |
Accept-Encoding | Compression format for trace events (gzip or snappy) |
The Nats-Trace-Only header is set by default, so your message is traced but never actually delivered or stored in JetStream. This enables safe tracing of message routing without any side effects.
This built-in approach sets NATS apart from other messaging systems, which typically require integration with external distributed tracing tools like Jaeger, Zipkin, or dedicated observability platforms. With NATS, the server handles everything—no sidecars, no agents, no additional infrastructure.
Each trace event contains four main fields:
Event types tell you what happened:
| Type | Meaning |
|---|---|
IN | Ingress—message arrived at this server |
EG | Egress—message left this server |
JS | JetStream—message was stored in a stream |
SE | Stream Export—message crossed an account boundary (export side) |
SI | Stream Import—message crossed an account boundary (import side) |
SM | Subject Mapping—subject was transformed via mapping rules |
Event kinds tell you the connection type:
| Kind | Connection Type |
|---|---|
| 0 | Client |
| 1 | Router (same cluster) |
| 2 | Gateway (cross-cluster) |
| 3 | System (internal) |
| 4 | Leaf Node |
| 5 | JetStream |
| 6 | Account (internal) |
As your message flows through the system, NATS builds a routing tree using the Nats-Trace-Hop header. Here’s how it works:
This tree structure lets you visualize exactly how your message fans out across the entire system.
Trace a message to see where it goes:
nats trace orders.new "test message"This shows condensed output with the routing tree visualization.
For the full trace data including all server responses:
nats trace orders.new "test message" --traceConsider a system with three clusters (C1, C2, C3), each with three servers, plus leaf nodes connected to C1 and C3. You have:
When you publish a trace message from a leaf node connected to C3:
1Client → C3L3 (leaf) → C3N2 (cluster node)2 ├→ C3N3 → JetStream3 ├→ C1N1 → C1N3 → C1L2 → Subscriber4 └→ C2N2 → C2N3 → SubscriberThe trace output shows this entire tree with connection types (leaf, router, gateway) and exactly which servers handled each hop.
By default, tracing stops at account boundaries for security. If your message crosses from one account to another via imports/exports, the trace won’t follow it unless you explicitly opt in.
To enable cross-account tracing, set allow_trace: true on your import or export configuration:
1exports: [2 { stream: "events.>", allow_trace: true }3]The rule: the receiver always opts in. For stream imports, the importer sets it. For service imports, the exporter sets it.
Message tracing in NATS gives you instant visibility into your distributed system without any external tools or code changes. Run a command, see where your messages go. One architecture, one tool—no need to cobble together separate monitoring solutions for each layer of your stack.
No more guessing why a message didn’t arrive. No more adding debug logging throughout your services. Just trace it.
Coming up next: We’ll cover how to integrate NATS message tracing with OpenTelemetry for production observability. Stay tuned.
We’re building the future of NATS at Synadia. Want more content like this? Subscribe to our newsletter
News and content from across the community