Stream backup and restore
The ORDERS stream holds every order Acme has ever taken. Replication
keeps it available when a node dies, but it doesn't protect you from a
mistake: an accidental nats stream purge, a bad migration, or a logic bug
that deletes the wrong messages. To recover from those you need a copy
the cluster can't touch — a point in time you can return to.
This page makes that copy and proves it works. It introduces two
operations and nothing else: taking a snapshot of ORDERS, and restoring the
stream from one.
A snapshot is a point-in-time copy
A snapshot is a complete copy of a stream as it exists at one instant: every message, the stream's configuration, and, if you ask for it, the state of its consumers. The server takes the snapshot and streams it to you in chunks; the CLI writes those chunks to a directory.
That directory holds two things. A backup.json file records the
stream's configuration and state: its subjects, retention, limits, and
sequence range. Alongside it, stream.tar.s2 is the messages
themselves, packed into a tarball and compressed with S2. Together
they're everything you need to recreate the stream from scratch.
Take a snapshot of ORDERS into a dated, off-site directory:
- CLI
#!/bin/bash
# Take a point-in-time snapshot of the ORDERS stream and write it to a
# dated, off-site directory. The snapshot is two files: a backup.json
# (the stream config + state) and a chunked, S2-compressed tarball
# (stream.tar.s2) carrying the messages.
#
# --consumers includes the durable consumer config and delivery
# position, so a restore brings back not just the messages but the
# shipping and analytics consumers exactly where they were.
nats stream backup ORDERS ./backups/orders/2026-06-04 --consumers
# Expected tail of the output:
#
# Starting backup of Stream "ORDERS" with 1 data file
# ...
# Received 4 MiB compressed data in 128 chunks for stream "ORDERS"
# in 0.41s, 4.0 MiB uncompressed
# Backup of "ORDERS" took 0.41s
#
# After this, ./backups/orders/2026-06-04/ holds backup.json and
# stream.tar.s2. Ship that directory off-site (see the cron + encrypt
# script on the page) — a snapshot left next to the live cluster does
# not survive the event that takes the cluster down.
The directory name carries the date on purpose. A snapshot is a point in
time, and naming it 2026-06-04 makes that explicit. Tomorrow's snapshot
goes in 2026-06-05, and you keep the dated snapshots under
./backups/orders/. We make that automatic later; here it's one
command.
The --consumers flag matters more than it looks. Without it, the
snapshot carries the messages but forgets the consumers reading them.
With it, the snapshot also records each durable consumer's config and
delivery position: the consumer state. Restore that snapshot and the
shipping and analytics consumers come back exactly where they left
off, not at the start of the stream. For recovery of a production stream,
use --consumers.
How the snapshot streams off the server
A snapshot doesn't arrive as one big download. The server cuts the tarball into chunks and pushes them to an inbox subject one at a time, waiting for the client to acknowledge each chunk before sending the next. That backpressure keeps a large stream from overwhelming a slow disk or a high-latency link.
The request lands on the snapshot API, the server answers with the
config and state, and then the message chunks flow to the inbox with an
ack per chunk until the tarball and backup.json are written to the
backup store. The two settings that govern this, chunk size and window
size, have sensible defaults, and you only change them when the
defaults time out. We cover them in the Pitfalls.
The full set of snapshot request options is documented in Reference → Snapshot Stream. We only need the behavior here.
Restore rebuilds the stream
A snapshot becomes useful when you turn it back into a stream. Restore reads a snapshot directory and recreates the stream from it: same messages, same sequence numbers, same configuration. If the snapshot included consumer state, restore brings the consumers back too.
Restore the directory you just wrote:
- CLI
#!/bin/bash
# Rebuild the ORDERS stream from a snapshot directory. Restore streams
# the tarball back into JetStream and recreates the stream byte for
# byte: same messages, same sequence numbers, same config, and — since
# this snapshot was taken with --consumers — the same shipping and
# analytics consumers at their saved delivery position.
#
# The stream must not already exist. Restore recreates it; it does not
# merge into a live stream.
nats stream restore ./backups/orders/2026-06-04
# Expected tail of the output:
#
# Starting restore of Stream "ORDERS" from ./backups/orders/2026-06-04
# ...
# Restored stream "ORDERS" in 0.38s
#
# Information for Stream ORDERS
# ...
# Messages: 1,000
# First Sequence: 1
# Last Sequence: 1,000
#
# Note: the stream name comes from backup.json. You cannot rename the
# stream on restore — if you need a copy under a new name, restore to
# the original name and then mirror or source it.
One rule shapes how you use restore: the stream name cannot change on
restore. The name lives in backup.json, and the server rejects a
restore that would land under a different name. A snapshot of ORDERS
restores as ORDERS, never as ORDERS_COPY. That keeps a restore
unambiguous: it rebuilds one stream under its own name rather than forking it
into a new one. If you
do need a second copy under a new name, restore to ORDERS first and
then mirror or source it, which
Mirrors and sources covers.
Restore also expects the stream not to already exist. It recreates the stream; it doesn't merge a snapshot into a live one. So a real recovery is: confirm the broken stream is gone (or remove it), then restore.
Verify the counts
A restore you didn't check is unverified. The last step is always to read the rebuilt stream's state back and confirm it matches the source.
- CLI
#!/bin/bash
# A restore you did not verify is a restore you do not have. After
# rebuilding ORDERS, read its state back and confirm the message count
# and last sequence match what the snapshot claimed.
#
# nats stream info prints both halves of the stream: the configuration
# you asked for and the state actually on disk. The numbers under State
# are the ones to check against the source.
nats stream info ORDERS
# Look at the State block:
#
# State:
#
# Messages: 1,000
# Bytes: 4.0 MiB
# First Sequence: 1
# Last Sequence: 1,000
# Active Consumers: 2
#
# Messages and Last Sequence must match the live stream you snapshotted.
# Active Consumers should show shipping and analytics back (because the
# snapshot used --consumers). If Messages is lower than expected, the
# snapshot was taken under live writes that did not finish — snapshot a
# quiesced stream, or accept the snapshot's point in time as your RPO.
Look at the State block. Messages and Last Sequence must match the
stream you snapshotted, and Active Consumers shows shipping and
analytics back if you used --consumers. Matching counts are what prove
the archive is real; the backup command exiting zero does not prove it on
its own.
Pitfalls
Four common pitfalls, each scoped to this page's two operations: snapshot and restore.
Memory streams cannot be snapshotted. A snapshot reads a stream's
on-disk files, so a stream with Storage: Memory has nothing to read.
The backup fails with memory streams do not support snapshots. Any stream
you want to back up needs file storage: set Storage: File when you
create it. Check the storage type before you rely on a snapshot, rather than
finding out during an incident:
- CLI
#!/bin/bash
# Take a point-in-time snapshot of the ORDERS stream and write it to a
# dated, off-site directory. The snapshot is two files: a backup.json
# (the stream config + state) and a chunked, S2-compressed tarball
# (stream.tar.s2) carrying the messages.
#
# --consumers includes the durable consumer config and delivery
# position, so a restore brings back not just the messages but the
# shipping and analytics consumers exactly where they were.
nats stream backup ORDERS ./backups/orders/2026-06-04 --consumers
# Expected tail of the output:
#
# Starting backup of Stream "ORDERS" with 1 data file
# ...
# Received 4 MiB compressed data in 128 chunks for stream "ORDERS"
# in 0.41s, 4.0 MiB uncompressed
# Backup of "ORDERS" took 0.41s
#
# After this, ./backups/orders/2026-06-04/ holds backup.json and
# stream.tar.s2. Ship that directory off-site (see the cron + encrypt
# script on the page) — a snapshot left next to the live cluster does
# not survive the event that takes the cluster down.
If that command errors on a memory stream, recreate the stream with file storage before it holds anything you can't lose.
The stream name cannot change on restore. As above, the server
rejects a restore that would rename the stream with stream name may not be changed during restore. Restore to the original name. If you need a
copy under a different name, restore first and then mirror or source it;
Mirrors and sources covers
that.
Flow control can time out on slow disks or distant links. The server
waits a few seconds for the client to acknowledge each chunk. On a slow
disk or a high-latency link the acknowledgment can arrive late, and the
backup aborts with a flow-control timeout (408 No Flow Response). The
fix is to send smaller chunks and a smaller window so each round trip is
cheaper:
# Smaller chunks and a smaller window survive a slow or distant link.
nats stream backup ORDERS ./backups/orders/2026-06-04 \
--consumers --chunk-size 64k --window-size 1m
--no-consumers silently drops consumer state. Skip --consumers
(or pass --no-consumers) and the snapshot carries messages only. The
restore rebuilds the stream with no consumers, and nothing warns you
until shipping is missing in production. Use --consumers for a full
recovery unless you plan to recreate every consumer by hand.
Where you are
You now have:
- A dated, off-site snapshot of
ORDERSunder./backups/orders/:backup.jsonplus a compressedstream.tar.s2. - A restore procedure that rebuilds the stream byte-identical, under its original name, with its consumers intact.
- A verification step that proves the restore by matching message counts.
This is your recovery point: the instant you can return to after an accidental delete or a logic error. On its own, though, it won't protect you from losing the whole site. For that you need a live copy somewhere else.
What's next
The next page stands up that live copy. A mirror of ORDERS at a
second site keeps a continuously updated copy you can fail over to, and
it distinguishes a snapshot (your recovery point) from a
mirror (your recovery time).
Continue to Mirrors and sources.
See also
- Reference → Snapshot Stream — every snapshot request option and its valid range.
- Reference → Restore Stream — the restore request schema.
- Reference → Snapshot Create Advisory — the event the server emits when a snapshot starts, for backup alerting.