All posts

Consumer Pausing in NATS 2.11: Hit Pause Without Losing Messages

Peter Humulock
Jan 6, 2026
Consumer Pausing in NATS 2.11: Hit Pause Without Losing Messages

NATS 2.11 introduced a simple but powerful JetStream feature: consumer pausing. It lets you temporarily halt message delivery to any consumer until a deadline you specify—without losing data, throwing errors, or disconnecting clients.

The Problem

You need to do some maintenance. Maybe it’s a database migration, a deployment, or you just need to stop processing for a bit while you debug a production issue.

Normally you’d have to stop your application or build custom logic to handle this gracefully. With consumer pausing, you don’t have to. Messages queue up, nothing gets lost, and when you’re ready, everything picks back up.

How It Works

  1. Your consumer receives messages like normal
  2. You send a config update with a pause deadline
  3. Message delivery suspends immediately (messages still flow to the stream)
  4. When the deadline passes, delivery resumes automatically—or you can resume early

That’s it. Works the same for both push and pull consumers.

Consumer pausing flow

The elegant part: your application doesn’t even know it’s happening. It just stops receiving messages. No errors, no disconnects. It keeps getting heartbeats like everything’s fine.

One thing to note: if there’s a batch already in flight (say you pulled 100 messages and you’re processing them), NATS won’t yank those away. It waits until the batch is done, then pauses before the next one.

When to Use It

  • Deployments and migrations — pause consumers while you roll out changes
  • Emergency maintenance — stop processing immediately without killing your app
  • Debugging production issues — freeze the flow while you investigate
  • Rate-limiting downstream services — give an overwhelmed dependency time to recover

Consumer pausing use cases

CLI Examples

Setup

First, create a stream to work with:

Terminal window
nats stream add events --subjects="events.>"

Push Consumer

Create a push consumer and subscribe:

Terminal window
# Create the consumer
nats consumer add events worker --deliver worker.inbox
# Subscribe to receive messages
nats sub worker.inbox

Start publishing messages (one per second):

Terminal window
nats pub events.test "message {{Count}}" --count=-1 --sleep=1s

Pause the consumer for 10 hours:

Terminal window
nats consumer pause events worker 10h

Messages stop flowing to your subscriber immediately. The stream still receives them—they’re just queued up waiting.

Check the consumer state:

Terminal window
nats consumer info events worker

You’ll see the pause deadline in the output.

Resume early when you’re ready:

Terminal window
nats consumer resume events worker

Messages start flowing again, and you catch up on everything that was published during the pause.

Extending the Pause

Need more time? You can extend (or shorten) the pause duration at any point by calling pause again with a new deadline:

Terminal window
# Originally paused for 10 minutes, but need another hour
nats consumer pause events worker 1h

The new deadline replaces the old one—no need to resume and re-pause. This is handy when maintenance takes longer than expected or you realize you need to pause for a different amount of time.

Pull Consumer

The flow is similar for pull consumers:

Terminal window
# Create a pull consumer
nats consumer add events puller
# Start pulling messages
nats consumer next events puller --count=1000

Pause with an automatic resume after 10 seconds:

Terminal window
nats consumer pause events puller 10s

Count to ten, and messages start flowing again automatically.

Pause on Creation

You can also initialize a consumer in a paused state:

Terminal window
nats consumer add events worker --pause=1h

Or use an absolute timestamp:

Terminal window
nats consumer add events worker --pause-until="2024-12-01T10:00:00Z"

Go Code Example

Here’s a simple worker that creates a consumer and processes messages:

1
func main() {
2
nc, _ := nats.Connect(nats.DefaultURL)
3
js, _ := jetstream.New(nc)
4
5
// Create stream
6
js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
7
Name: "events",
8
Subjects: []string{"events.>"},
9
})
10
11
// Create consumer
12
consumer, _ := js.CreateOrUpdateConsumer(ctx, "events", jetstream.ConsumerConfig{
13
Name: "worker",
14
})
15
16
// Start consuming in a goroutine
17
go func() {
18
consumer.Consume(func(msg jetstream.Msg) {
19
fmt.Printf("Received: %s\n", string(msg.Data()))
20
msg.Ack()
21
})
22
}()
23
24
// Publish messages every second
25
for {
26
js.Publish(ctx, "events.test", []byte("hello"))
27
time.Sleep(time.Second)
28
}
29
}

And a separate program to pause/resume:

1
func main() {
2
nc, _ := nats.Connect(nats.DefaultURL)
3
js, _ := jetstream.New(nc)
4
5
stream, _ := js.Stream(ctx, "events")
6
consumer, _ := stream.Consumer(ctx, "worker")
7
8
if os.Args[1] == "resume" {
9
consumer.Resume(ctx)
10
fmt.Println("Consumer resumed")
11
} else {
12
deadline := time.Now().Add(time.Hour)
13
consumer.Pause(ctx, deadline)
14
fmt.Println("Consumer paused for 1 hour")
15
}
16
}

Run the worker, then pause and resume from another terminal:

Terminal window
# Terminal 1
go run worker.go
# Terminal 2
go run pause.go pause # Messages stop
go run pause.go resume # Messages flow again

Wrapping Up

Consumer pausing is simple but incredibly useful—no more custom backpressure logic just to temporarily halt processing. It’s the kind of feature that comes from years of seeing how people actually use NATS.

Pause when you need to, resume when you’re ready, and never lose a message.


We’re building the future of NATS at Synadia. Want more content like this? Subscribe to our newsletter

Get the NATS Newsletter

News and content from across the community


© 2026 Synadia Communications, Inc.
Cancel