A Valkey restore usually fails quietly. You copy last night's dump.rdb into the data directory of a node that runs with appendonly yes, restart it, and it comes up empty or with the very data you meant to replace. The log says DB loaded from append only file, or Creating AOF base file appendonly.aof.1.base.rdb on server start, and never mentions your RDB. Or the CI job that runs valkey-check-aof on every backup starts failing after a 9.0 or 9.1 upgrade with AOF appendonly.aof.1.base.rdb is not valid, while the backup is fine. This guide covers how Valkey 9.x persists data, what a consistent backup is for a standalone node and a cluster, how to verify the files, and restore drills that measure your real recovery point and recovery time.
- With
appendonly yes, startup loads onlyappendonlydirand ignoresdump.rdb. Restore an RDB with AOF off, check it, then turn AOF on withCONFIG SET. - A Valkey Cluster has no cluster-wide snapshot. Each shard saves at its own moment unless you pause writes on every primary first.
valkey-check-aofbefore 9.0.7 and 9.1.3 reports healthy 9.x AOF files as invalid. Use the patched tool and never run--fixon that result.- A primary that loads keys for slots another node owns deletes them at startup. Keep each shard backup's slot map with the file.
- Measure RPO and RTO in drills; the numbers come from your logs, not from a vendor page.
Scope and review date: November 2026. This post covers self-managed Valkey 9.0 and 9.1 (latest patches 9.0.7 and 9.1.3, released 26 October 2026), standalone and Valkey Cluster, with notes on 8.x. Managed services such as Amazon ElastiCache have their own snapshot features and are out of scope. Defaults, log lines, and tool output were checked against the Valkey documentation, release notes, and the 9.1.3 source on 10 November 2026. The commands were not run in a lab, so rehearse them on a scratch host first.
How does Valkey write data to disk?
RDB writes a point-in-time snapshot to dump.rdb: the server forks, the child writes a temporary file, and the file is renamed over the old one when complete. The Valkey persistence documentation says that rename makes copying dump.rdb on a running server safe. With no save lines set, the 9.1.3 valkey.conf saves after 3600 seconds and 1 change, 300 seconds and 100 changes, or 60 seconds and 10000 changes.
The append-only file (AOF) logs every write. It is off by default, and when on, Valkey keeps a multi-part set in appendonlydir: one base file, one or more incremental files, and a manifest that lists them in order. With the default aof-use-rdb-preamble yes, the base file is RDB-format.
$ cat /var/lib/valkey/appendonlydir/appendonly.aof.manifest
file appendonly.aof.1.base.rdb seq 1 type b
file appendonly.aof.1.incr.aof seq 1 type iInterpretation: each line names a file, its sequence, and its type: b base, i incremental, h history awaiting deletion (aof.c, 9.1.3). Without the manifest the other files cannot be loaded in order.
Multi-part AOF: manifest, base file, and incremental files
The manifest drives the load order, and a rewrite replaces the whole set at once
Back up the set, not a file: an incremental file without its base and manifest cannot be loaded. Copy the whole appendonlydir with automatic rewrites paused, as the persistence documentation describes.
Two 9.x details change what is on disk. Valkey 9.0 and later write RDB version 80 with a VALKEY signature instead of REDIS, including the base file in appendonlydir. And from 9.1.0, a replica with AOF on reuses the RDB from a disk-based full sync as its new AOF base file (9.1.0-rc1 notes, #1901). The source renames dump.rdb into appendonlydir, so a job that copies dump.rdb from such a replica can find it missing.
What counts as a consistent backup of a standalone node?
An RDB is consistent by construction: it is the dataset at the fork. The question is its age. With default save points, the file on disk can be an hour old on a quiet node, so trigger BGSAVE and wait for LASTSAVE to change.
CLI="valkey-cli -h 127.0.0.1 -p 6379"
before=$($CLI LASTSAVE)
$CLI BGSAVE
while [ "$($CLI LASTSAVE)" = "$before" ]; do sleep 2; done
$CLI INFO persistence | grep '^rdb_last_bgsave_status'Interpretation: a changed LASTSAVE means the snapshot finished; copy the file only if the status reads ok, and add a timeout to the loop. BGSAVE fails while an AOF rewrite runs, and 8.1 added BGSAVE CANCEL (BGSAVE reference).
For the AOF, the persistence documentation warns that a copy taken during a rewrite can be invalid. Pause automatic rewrites, confirm none is running, copy, and restore the setting.
prev=$($CLI CONFIG GET auto-aof-rewrite-percentage | tail -1)
$CLI CONFIG SET auto-aof-rewrite-percentage 0
$CLI INFO persistence | grep '^aof_rewrite_in_progress' # must be 0
DEST=/backup/aof-$(date -u +%Y%m%dT%H%MZ); mkdir -p "$DEST"
cp -l /var/lib/valkey/appendonlydir/* "$DEST"/
$CLI CONFIG SET auto-aof-rewrite-percentage "$prev"Interpretation: the documentation allows hard links here, because Valkey only appends to these files or replaces them whole, so rewrites can resume at once and you copy the links afterwards. Hard links need the same filesystem.
RDB, AOF, or both: what you back up and what startup loads
Switch modes to compare the file you ship with the file a restart reads
RDB only
| Question | RDB only |
|---|---|
| Files on disk | dump.rdb |
| Startup loads | dump.rdb |
| Local loss window (docs) | Writes since the last snapshot |
| Backup artifact | One file, safe to copy live |
| Restore | Stop, replace dump.rdb, start |
AOF only
| Question | AOF only |
|---|---|
| Files on disk | appendonlydir |
| Startup loads | The files the manifest lists |
| Local loss window (docs) | About one second with everysec |
| Backup artifact | The whole directory, rewrites paused |
| Restore | Stop, replace the directory, start |
RDB and AOF
| Question | RDB and AOF |
|---|---|
| Files on disk | Both |
| Startup loads | The AOF set only; dump.rdb is ignored |
| Local loss window (docs) | About one second with everysec |
| Backup artifact | A fresh dump.rdb from BGSAVE |
| Restore | Load the RDB with AOF off, then enable AOF live |
The mixed mode is the trap: the easiest file to ship is the one a restart ignores, and the documentation suggests running both for stronger durability. Plan that restore path explicitly.
How do you back up a Valkey Cluster?
Each primary persists only its own slots and forks at its own moment; no command snapshots the whole cluster at one instant. Per-shard files a few seconds apart suit most caches. They do not suit an application with invariants across keys on different shards.
In the 9.1.3 source, valkey-cli --cluster backup pulls an RDB from each primary in turn, writes valkey-node-<ip>-<port>-<id>.rdb, and saves the topology with slot ranges to nodes.json.
DEST=/backup/cluster-$(date -u +%Y%m%dT%H%MZ); mkdir -p "$DEST"
valkey-cli --cluster backup 10.0.2.11:6379 "$DEST"
valkey-cli -h 10.0.2.11 CLUSTER SHARDS > "$DEST"/shards.txtInterpretation: each primary forks to serve its transfer, so the files' times differ. Keep nodes.json and the CLUSTER SHARDS output with them, and do not back up while valkey-cli --cluster check reports open slots.
For one point in time, freeze writes on every primary for the length of the forks. Per the CLIENT PAUSE reference, WRITE mode blocks writes and also stops expiry and eviction. BGSAVE replies after its fork, so once every primary replies, every snapshot holds the same frozen state.
P="10.0.2.11 10.0.2.12 10.0.2.13"
for h in $P; do valkey-cli -h $h INFO persistence | grep '^aof_rewrite_in_progress'; done
for h in $P; do valkey-cli -h $h CLIENT PAUSE 10000 WRITE; done
for h in $P; do valkey-cli -h $h BGSAVE; done
for h in $P; do valkey-cli -h $h CLIENT UNPAUSE; doneInterpretation: every first-loop line must read aof_rewrite_in_progress:0, or BGSAVE fails. The 10-second pause caps a crashed script. This is our reading of documented commands, not a published Valkey procedure, so test the pause against client timeouts.
How should you ship backups off the host?
The persistence documentation suggests hourly and daily copies, at least one transfer a day off the machine, gpg -c encryption for cloud storage, a size and digest check after transfer, and an independent alert when fresh backups stop arriving.
NAME=prod-cache-01-$(date -u +%Y%m%dT%H%MZ).rdb; cd /backup
cp /var/lib/valkey/dump.rdb "$NAME" && valkey-check-rdb "$NAME"
valkey-cli INFO keyspace > "$NAME.keyspace.txt"
gpg --batch --pinentry-mode loopback --passphrase-file /etc/valkey/backup.pass -c "$NAME"
sha256sum "$NAME.gpg" > "$NAME.gpg.sha256"
aws s3 cp . s3://example-valkey-backups/prod-cache-01/ --recursive --exclude "*" --include "$NAME*"Interpretation: the file is checked before it leaves, and its key counts travel with it for the drill to compare. Paths are placeholders. Without data-directory access, valkey-cli --rdb <file> pulls a snapshot remotely; the valkey-cli documentation says to check its exit code.
It holds keys and functions, not valkey.conf, the aclfile, TLS certificates, or a cluster node's nodes.conf. Back those up too. 9.0.7 and 9.1.3 also fix a bug that could publish a truncated nodes.conf (#4489).
How do you verify RDB and AOF files before you need them?
valkey-check-rdb parses the whole file, verifies the CRC64 checksum when the file has one, and exits with 0 on success. A good file ends with \o/ RDB looks OK! \o/; a newer or foreign version ends with \o/ RDB looks OK, but loading requires config 'rdb-version-check relaxed'.
valkey-check-rdb prod-cache-01-20261110T0200Z.rdb; echo "exit=$?"
valkey-check-rdb prod-cache-01-20261110T0200Z.rdb --stats --format infoInterpretation: treat any non-zero exit as a failed backup. --stats, from 9.0's RDB analysis reports (#1743), gives per-type counts worth diffing between backups.
valkey-check-aof takes the manifest, checks every file in order, and ends with All AOF files and manifest are valid. It has a known false failure: since 9.0 the base file starts with VALKEY, but the tool recognized only REDIS, so it parsed the base file as text commands. PR #4739 fixed it in 9.1.3 and 9.0.7. The server's loader was never affected.
A false failure from an unpatched valkey-check-aof
Illustrative output, built from the 9.1.2 source strings: a healthy 9.1 AOF reported as invalid
The tell: a .base.rdb file checked in "RESP format" with ok_up_to=0. Upgrade the tool to 9.1.3 or 9.0.7 and check again; do not run --fix. The fix is in the 9.0.7 release notes.
In automation, remember that --fix is interactive (Continue? [y/N]) and truncates only the last file in the manifest, so never pipe yes into it. Pin the check tools in your backup image to the servers' patch.
Why does Valkey ignore dump.rdb when appendonly is yes?
Because startup never reads it. In the 9.1.3 loadDataFromDisk(), AOF on means only the AOF loader runs. Drop a backup dump.rdb into such a node and:
- if
appendonlydirstill holds files, they load and the backup is ignored; - if you emptied it, the node starts empty and writes a new empty base file;
- either way, the next save, or the save
SHUTDOWNperforms when save points exist, overwrites your restoreddump.rdb.
grep -E 'DB loaded from|Creating AOF base file' /var/log/valkey/valkey-server.log | tail -2
valkey-cli INFO persistence | grep '^aof_enabled'
valkey-cli DBSIZEInterpretation: DB loaded from disk means the RDB loaded; DB loaded from append only file means the AOF did. aof_enabled:1 with a DBSIZE far from the backup's recorded count means you hit the trap.
The safe path is the documentation's live switch to AOF: start with AOF off so the RDB loads, then CONFIG SET appendonly yes, which writes a new base file from memory. Changing only the config file and restarting "can result in data loss", in the documentation's words.
Attached replicas resync from whatever the primary loads, so an empty primary empties them, as the replication documentation describes. Stop them until the restore is verified.
How do you run a restore drill for a standalone node?
A drill restores a real backup onto a scratch host and times each stage. Run one after every upgrade and every backup pipeline change.
Restore drill: one RDB backup onto a scratch host
Eight steps, no outage: time each one, because the total is your measured restore time
-
Pick the newest backup and note its time
Its age is the recovery point an incident would have given you.
-
Download it and verify the digest
A mismatch is a pipeline finding in itself.
cd /restore && aws s3 cp \ s3://example-valkey-backups/prod-cache-01/ . \ --recursive --exclude "*" \ --include "prod-cache-01-20261110T0200Z*" sha256sum -c *.gpg.sha256 -
Decrypt and check the file
Use the check tool from the patch you restore onto.
gpg --batch --pinentry-mode loopback \ --passphrase-file backup.pass -o dump.rdb \ -d prod-cache-01-20261110T0200Z.rdb.gpg valkey-check-rdb dump.rdb -
Start a scratch server with persistence off
AOF off makes the RDB load; an empty save list stops a shutdown save from overwriting it.
valkey-server --port 6390 --dir /restore \ --dbfilename dump.rdb --appendonly no \ --save "" --daemonize yes \ --logfile /restore/drill.log -
Confirm the load and record its time
The log line includes the load time in seconds.
grep 'DB loaded from disk' /restore/drill.log valkey-cli -p 6390 INFO keyspace -
Compare with the backup record and spot-check keys
Expired keys drop at load, so expect small gaps only. Read a few known keys: counts can match while content is wrong.
cat prod-cache-01-20261110T0200Z.rdb.keyspace.txt valkey-cli -p 6390 --scan --pattern 'session:*' \ --count 1000 | head -5 -
Rehearse the AOF switch-on
If production runs AOF, enable it and wait for the rewrite to report ok.
valkey-cli -p 6390 CONFIG SET appendonly yes valkey-cli -p 6390 INFO persistence | grep -E \ 'aof_rewrite_in_progress|aof_last_bgrewrite_status' -
Record the timings and shut down
Download, decrypt, check, load, and rewrite times add up to your measured restore time for this dataset.
valkey-cli -p 6390 SHUTDOWN NOSAVE
Main risk: a drill that only counts keys passes on a backup of the wrong node. The spot-check in step 6 is the one teams skip.
A production restore of a standalone primary follows the same order inside an outage window. Stop the replicas, stop the primary, move appendonlydir aside, copy in the verified dump.rdb, and set appendonly no in valkey.conf for one start.
sudo systemctl start valkey # valkey.conf has appendonly no for this start
valkey-cli DBSIZE
valkey-cli CONFIG SET appendonly yes
valkey-cli INFO persistence | grep -E '^aof_rewrite_in_progress|^aof_last_bgrewrite_status'
valkey-cli CONFIG REWRITE # persists appendonly yesInterpretation: wait for aof_rewrite_in_progress:0 and aof_last_bgrewrite_status:ok before CONFIG REWRITE, then start the replicas, which full-sync the restored data. Forget CONFIG REWRITE and the next restart runs with AOF off.
How do you restore a Valkey Cluster from per-shard backups?
The rule that matters is verifyClusterConfigWithData(), which a primary runs after loading data at startup and on promotion. For each slot holding keys: if no node owns it, the node logs I have keys for unassigned slot N. Taking responsibility for it. and claims it; if another node owns it, the node logs ... Deleting keys in the slot. and deletes them. The same strings are in the 8.0.12, 8.1.11, 9.0.7, and 9.1.3 cluster_legacy.c sources.
That gives a rebuild path. Start one fresh primary per shard backup, each with an empty directory, no nodes.conf, AOF off, and that shard's RDB as dump.rdb. Each claims the slots that hold keys. Then join the nodes and assign the empty slots from your saved map.
# On each new primary, before its first start
cp valkey-node-10.0.1.11-6379-<old-id>.rdb /var/lib/valkey/dump.rdb
valkey-server /etc/valkey/valkey.conf --appendonly no --daemonize yes
grep 'Deleting keys in the slot' /var/log/valkey/valkey-server.log # must print nothing
# Then join and fill the gaps
valkey-cli -h 10.0.2.11 CLUSTER MEET 10.0.2.12 6379
valkey-cli -h 10.0.2.11 CLUSTER MEET 10.0.2.13 6379
valkey-cli --cluster check 10.0.2.11:6379
valkey-cli -h 10.0.2.13 CLUSTER ADDSLOTSRANGE 16300 16383 # example empty range
valkey-cli -h 10.0.2.11 CLUSTER INFO | grep -E 'cluster_state|cluster_slots_assigned'Interpretation: while slots are missing, the check prints [ERR] Not all 16384 slots are covered by nodes. Assign each gap to its owner in nodes.json with CLUSTER ADDSLOTSRANGE until cluster_state:ok, then add replicas with CLUSTER REPLICATE and enable AOF as above. This path follows from the source, not a published runbook, so rehearse it.
The same rule makes shortcuts dangerous. A shard's RDB copied onto a node whose nodes.conf owns other slots is deleted on startup. Stop one primary that has a replica in a live cluster to restore it, and the replica is promoted after cluster-node-timeout; the restored node rejoins as a replica and full-syncs over your data, as the cluster specification describes. Restore a whole cluster in a window, or into a separate cluster.
For a different shard layout, load each RDB into a standalone staging instance and run valkey-cli --cluster import <target> --cluster-from <staging> --cluster-copy. Without --cluster-copy, its MIGRATE calls delete each key from the source. For replacing failed nodes in a live cluster, see replacing a failed Valkey Cluster node.
Can an older Valkey version restore a newer backup?
Not by default. Valkey 7.2 and 8.x write RDB version 11; 9.0 and 9.1 write 80. With the default rdb-version-check strict, 8.1 logs Can't handle RDB format version 80, and 8.0 rejects the VALKEY signature with Wrong signature trying to load DB from file. Valkey cannot load RDB files from Redis CE 7.4 and later. Restore onto the backup's patch or newer, and avoid 9.1.0 to 9.1.2 for 9.0 files: 9.1.3 fixes a listpack incompatibility that "caused RDB load and replication failures for entries of certain lengths between 9.0 and 9.1.x" (#4819). Rollbacks are covered in Valkey RDB versions 11, 80, and 81.
What recovery point and recovery time can you honestly claim?
Your recovery point objective (RPO) is the age of the newest backup that survives the incident. appendfsync everysec limits loss to about one second only while the disk survives. Lose the host, and the RPO is your shipping interval plus transfer time. In a cluster each shard has its own RPO unless you used a write-paused backup.
Your recovery time objective (RTO) is the sum of the stages your drill timed, plus replica full sync and client reconnection. During a load, INFO persistence shows loading_loaded_perc and loading_eta_seconds, and clients get LOADING errors. We quote no typical figures: load time depends on dataset size, value types, and hardware.
valkey-cli INFO persistence | grep -E '^loading:|^loading_eta_seconds'Interpretation: record when loading:0 appears; the application outage lasts at least that long.
Point-in-time recovery is narrow. With aof-timestamp-enabled yes (default no), valkey-check-aof --truncate-to-timestamp <unix-ts> <manifest> cuts the log back, but in 9.1.3 only within the last file, and never before the current base. Run it on a copy.
Frequently asked questions
Why does Valkey start empty after I restore dump.rdb?
Because AOF is enabled. With appendonly yes, Valkey loads only appendonlydir at startup and never reads dump.rdb; if that directory is empty, it starts with no keys and writes a new empty base file. Start once with appendonly no so the RDB loads, check the key count, then run CONFIG SET appendonly yes and CONFIG REWRITE.
Why does valkey-check-aof say my AOF is not valid after upgrading to Valkey 9?
Probably a false failure. From 9.0 the RDB-format base file starts with a VALKEY signature, and valkey-check-aof before 9.0.7 and 9.1.3 recognized only REDIS, so it misread the base file. The output shows the base checked in RESP format with ok_up_to=0. Recheck with a 9.0.7 or 9.1.3 tool, and do not run --fix.
Can I take a consistent backup of a whole Valkey Cluster?
Not with one command. Each primary snapshots its own slots at its own moment, and valkey-cli --cluster backup pulls them one after another. For a single point in time, pause writes on every primary with CLIENT PAUSE WRITE, run BGSAVE on each, then unpause. Test the pause against your client timeouts first.
Can Valkey 8 restore a backup taken on Valkey 9?
Not by default. Valkey 9.0 and 9.1 write RDB version 80 with a VALKEY signature. Valkey 8.1 with the default rdb-version-check strict refuses it, and 8.0 rejects the signature. The relaxed setting on 8.1 is best-effort and aborts on unknown data, so restore onto the backup's Valkey patch or a newer one.
Key takeaways
- With AOF on, startup reads only
appendonlydir. Restore an RDB with AOF off, verify it, enable AOF live, and runCONFIG REWRITE. - Ship a fresh
BGSAVEsnapshot or a rewrite-paused AOF directory, with key counts, a digest, the config, and the ACL file. - A cluster backup is a set of per-shard snapshots plus a slot map; a primary deletes keys for slots another node owns.
- Check every backup with
valkey-check-rdband a 9.0.7 or 9.1.3valkey-check-aof. - RPO is the age of your newest off-host copy, and RTO is your timed drill, so drill after every upgrade.
Plan Valkey backups and restore drills with JusDB
JusDB's database backup and disaster recovery team can review your Valkey persistence settings and backup pipeline, write restore runbooks for standalone nodes and clusters, and run restore drills with you that measure your recovery point and recovery time.
For incidents on a running deployment, such as a corrupted AOF or a failed restore, see Valkey support. Your team approves every production change.