Redis: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk
Hi, it's BlueByte. The application is up, Redis answers PING, GET still returns data — and every single write comes back as (error) MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error. Nothing crashed. Redis is refusing writes on purpose because its last background save failed, and it wants you to find out now rather than at restore time. We'll walk through the two MISCONF replies, the four failures behind them, how to tell which one you have, the fix per cause, and how to be told next time before your users tell you.
Two MISCONF replies, and only one of them is about the snapshot
Redis has two errors that start with this prefix, and they point at different subsystems. The long one above is the RDB reply, raised when snapshotting is on and the last background save failed. The other is short and carries the operating system's own message:
(error) MISCONF Errors writing to the AOF file: No space left on deviceThat one is the append-only file, not the snapshot, and it is driven by aof_last_write_status rather than the RDB state. Read the whole reply before you start: the word after MISCONF decides which half of INFO persistence you care about.
Why one failed background save stops your writes on purpose
This is not a bug or a safety fuse that tripped by accident. It is the default, and redis.conf explains itself:
# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
stop-writes-on-bgsave-error yesTwo consequences worth holding onto. It only applies when at least one save point is configured, so an instance started with save "" never enters this state at all. And it clears itself: the moment one background save succeeds, writes are accepted again, with no restart and no config change. That is why the fix below ends with a single BGSAVE rather than a service restart.
The four failures behind it: space, permissions, a read-only mount, and fork
Redis writes the snapshot to a temporary file inside dir and renames it over dbfilename (dump.rdb by default) only once the file is complete. Each step fails with its own log line, which is what makes the log worth reading first:
- The disk filled.
Write error while saving DB to the disk(...): No space left on device. Often the volume is shared with application logs, or old orphanedtemp-*.rdbfiles from children that were killed mid-save are still sitting there. - The directory isn't writable by the redis user.
Failed opening the temp RDB file temp-2481.rdb (in server root dir /var/lib/redis) for saving: Permission denied. Typical after a container bind mount, a restore from backup unpacked as root, or a policy change. - The rename step hit a read-only filesystem.
Error moving temp DB file temp-2481.rdb on the final destination dump.rdb (in server root dir /var/lib/redis): Read-only file system. The child wrote the whole snapshot and then could not put it in place. - fork() failed.
Can't save in background: fork: Cannot allocate memory. Redis forks a child to write the snapshot and relies on copy-on-write, so in theory the child costs almost nothing. The FAQ explains why the kernel disagrees: withovercommit_memoryat zero, Linux can't know in advance how many pages will change, so the fork fails unless there is as much free RAM as duplicating every parent page would take. In the docs' own example, a 3 GB dataset with 2 GB free will fail.
Read INFO persistence and the log before you change anything
Start with the state that decides whether writes are blocked, then the configuration it depends on:
redis-cli info persistence | grep -E 'rdb_last_bgsave_status|rdb_changes_since_last_save|rdb_last_save_time|aof_enabled|aof_last_write_status'
redis-cli config get dir dbfilename save stop-writes-on-bgsave-errorrdb_changes_since_last_save:184213
rdb_last_save_time:1758501902
rdb_last_bgsave_status:err
aof_enabled:0
aof_last_write_status:ok1) "dir"
2) "/var/lib/redis"
3) "dbfilename"
4) "dump.rdb"
5) "save"
6) "3600 1 300 100 60 10000"
7) "stop-writes-on-bgsave-error"
8) "yes"rdb_last_bgsave_status is documented as the status of the last RDB save operation, and err is the flag that turns writes off. rdb_last_save_time is the epoch timestamp of the last successful save — run date -d @1758501902 to see how long this instance has been running with nothing on disk, and rdb_changes_since_last_save to see how much would be lost. If aof_enabled is 1 and aof_last_write_status is err, you are in the AOF variant instead.
Now get the reason, which is only in the log and the filesystem:
sudo journalctl -u redis-server --since '2 hours ago' | grep -iE 'saving|fork|rdb'
df -h /var/lib/redis
df -i /var/lib/redis
stat -c '%n %U:%G %a' /var/lib/redis
sudo -u redis test -w /var/lib/redis && echo "writable by redis" || echo "NOT writable by redis"
cat /proc/sys/vm/overcommit_memoryRead the result as a short tree. A No space left on device line with df -h at 100% is the disk; df -i at 100% with space free is inodes. Permission denied with stat showing an owner other than redis is the directory. fork: Cannot allocate memory with /proc/sys/vm/overcommit_memory reading 0 is the kernel setting. Nothing here writes anything, so it is safe to run on the live instance.
Fix the cause, then prove it with one BGSAVE
Each cause has its own repair, and none of them needs a restart:
# Disk full: find what is using the volume, then list temp files left by killed children.
sudo du -xh --max-depth=1 /var/lib/redis | sort -h | tail -5
sudo find /var/lib/redis -name 'temp-*.rdb' -mmin +60 -ls
# Delete them only once no save is running — removing an in-flight child's
# temp file breaks that save. rdb_bgsave_in_progress must read 0.
redis-cli info persistence | grep rdb_bgsave_in_progress
sudo find /var/lib/redis -name 'temp-*.rdb' -mmin +60 -delete
# Directory not writable: hand it back to the redis user.
sudo chown redis:redis /var/lib/redis
sudo chmod 750 /var/lib/redis
# fork: Cannot allocate memory — the setting the docs ask for, now and on reboot.
sudo sysctl vm.overcommit_memory=1
echo 'vm.overcommit_memory = 1' | sudo tee /etc/sysctl.d/60-redis-overcommit.confThen ask Redis to try again and watch the flag clear:
redis-cli bgsave
sleep 5
redis-cli info persistence | grep -E 'rdb_last_bgsave_status|rdb_last_save_time'
redis-cli set bluebyte:canary okBackground saving started
rdb_last_bgsave_status:ok
rdb_last_save_time:1758616355
OKThe OK on the last line is the whole point: writes came back by themselves, because the status flipped, not because anything was restarted.
A worked example: a session store that filled its own volume
A session-store Redis on a 20 GB volume starts rejecting writes at 02:10. INFO persistence shows rdb_last_bgsave_status:err and an rdb_last_save_time four hours old; df -h reports the volume 100% full; the log carries Write error while saving DB to the disk(...): No space left on device repeating every save point. du finds a 5.8 GB dump.rdb, three orphaned temp-*.rdb files from earlier saves whose children were killed, and the application's own log directory on the same volume. The operator moves the logs to another filesystem, removes the orphaned temp files, and checks df -h reads 44% before touching Redis at all. Then redis-cli bgsave answers Background saving started, rdb_last_bgsave_status reads ok a few seconds later, and the next SET succeeds — no restart, no lost keys beyond what was already unsaved. The follow-up is the real fix: dir gets its own filesystem so an application log can never take Redis down again.
Turning the switch off is a workaround, and here is what it hides
If you need writes back this second and you accept the trade, there is a documented switch:
redis-cli config set stop-writes-on-bgsave-error noThe redis.conf comment is specific about when that is reasonable: if you have set up proper monitoring of the server and its persistence, you may want to disable the feature so Redis keeps working even when there are problems with disk or permissions. Note what you are buying. Writes resume instantly, the snapshot is still failing, and the only thing that changed is that nobody is being told. If the process restarts before a save succeeds, everything since rdb_last_save_time is gone. CONFIG SET also does not survive a restart unless you follow it with CONFIG REWRITE, so an instance "fixed" this way often re-blocks after the next deploy. Use it as a deliberate choice on a cache you can afford to lose, and keep an alert on the status either way.
Keep it from coming back
Alert on rdb_last_bgsave_status rather than on PING — an instance in this state answers PING perfectly. Size the machine for saving, not just for the dataset: the administration guide notes that in a write-heavy application, while saving an RDB file or rewriting the AOF, Redis can use up to twice the memory it normally uses, and it recommends setting an explicit maxmemory below physical RAM, so on a box with 10 GB free you set 8 or 9. Put vm.overcommit_memory = 1 in /etc/sysctl.conf as the same guide asks, and disable transparent huge pages with echo never > /sys/kernel/mm/transparent_hugepage/enabled. Give dir its own filesystem, and monitor free space against the size of dump.rdb, not against zero — a save needs room for a second copy before the rename. If an instance really is a pure cache, say so in the config with save "" instead of leaving save points enabled and the safety switch off.
How this differs from OOM command not allowed and READONLY
(error) OOM command not allowed when used memory > 'maxmemory'. is the memory limit, not the disk: the dataset hit maxmemory and the eviction policy has nothing it is allowed to evict. Reads work there too, which is why the two get confused, but INFO persistence will show rdb_last_bgsave_status:ok and the fix is maxmemory, the eviction policy, or less data. (error) READONLY You can't write against a read only replica. means you are connected to a replica rather than the primary — a routing or failover problem, with nothing wrong on disk at all. Check which error string you actually got before reaching for any of these.
Next time writes stop while reads keep working, walk these checks back in order: which MISCONF reply is it, what does rdb_last_bgsave_status say, what did the log record at the last save attempt, and only then decide between fixing the disk and accepting the trade.
Related questions
Do I have to restart Redis after fixing the disk?
No. stop-writes-on-bgsave-error is self-clearing: once a background save succeeds, Redis allows writes again automatically. Run redis-cli bgsave and watch rdb_last_bgsave_status change from err to ok, then try a write. A restart is the riskier move here, because anything not yet saved is only in memory.
Reads still work. Why does Redis block only writes?
The reply says it: commands that may modify the data set are disabled. The dataset in memory is intact and readable; what failed is getting a copy of it onto disk. Blocking writes stops the in-memory and on-disk versions from drifting further apart while the problem is unfixed.
I get MISCONF but my save line is empty.
Then it is the AOF variant, which reads MISCONF Errors writing to the AOF file: followed by the operating system's message. Check aof_enabled and aof_last_write_status in INFO persistence rather than the rdb_ fields. With save "" and no AOF, this error cannot appear at all.
Is stop-writes-on-bgsave-error no safe to leave in place?
Only with monitoring, which is the condition redis.conf itself sets. It restores writes and changes nothing about the failing save, so an instance that restarts before a save succeeds loses everything since rdb_last_save_time. If you do set it, persist it with CONFIG REWRITE and alert on rdb_last_bgsave_status, or you have traded a loud failure for a silent one.
fork failed with Cannot allocate memory, but free shows plenty. What now?
That is the documented overcommit case. With vm.overcommit_memory at 0, Linux refuses the fork unless there is enough free RAM to duplicate every page of the parent, even though copy-on-write means most of them are never copied. Set vm.overcommit_memory=1 and persist it in sysctl, and remember a write-heavy instance can use up to twice its normal memory while saving.
References
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
More in this category
Git: fatal: detected dubious ownership in repository
Since the CVE-2022-24765 fix in Git 2.35.2, Git refuses to read a repository whose working tree or .git directory is owned by a different user than the one running the command. It shows up in containers, CI jobs, sudo sessions and shared drives. Fix the ownership if the repo should be yours, or add the exact path to safe.directory in your global config — never in the repo's own config, which Git ignores for this.
Kubernetes: Internal error occurred: failed calling webhook
An admission webhook sits in front of your write, the API server could not get an answer out of it, and failurePolicy: Fail turned that silence into a rejection. The tail of the message is the whole diagnosis: context deadline exceeded means the call went nowhere, no endpoints available means nothing is running, and an x509 line means the API server does not trust the webhook's certificate. Each has a different fix, and none of them is your manifest.
MySQL: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
A statement waited the full innodb_lock_wait_timeout for a row lock another transaction is still holding, and gave up. sys.innodb_lock_waits names the blocking session and hands you the KILL statement, and a blocking_query of NULL means the blocker is idle on an open transaction. The detail most retry loops get wrong: by default only the timed-out statement is rolled back, so your transaction is still open and still holds every lock it took earlier.
Node.js: FATAL ERROR: Reached heap limit — JavaScript heap out of memory (exit 134)
The V8 heap has its own ceiling, derived from system memory and the Node release, and it is often far below the RAM you have; when a build or server reaches it, V8 aborts with FATAL ERROR: Reached heap limit and exit code 134. Read the real limit with v8.getHeapStatistics().heap_size_limit, then raise it with --max-old-space-size (in MiB) or NODE_OPTIONS for a large workload, size it below the cgroup limit inside containers, and use --heapsnapshot-near-heap-limit to catch a leak in a long-running process. Exit 137 with no FATAL ERROR line is a container kill, not this.
Docker: "exec format error" when the container starts — wrong-platform image, no emulator, or a script with no shebang
The container exits on its first instruction with exec format error — the kernel's ENOEXEC, meaning the file exists but cannot be executed here. In practice that is an image built on one CPU architecture (an Apple-silicon Mac produces linux/arm64) and run on another (an x86_64 server) with no QEMU handler registered in binfmt_misc, or an entrypoint script whose first line is not a shebang. uname -m, docker image inspect and ls /proc/sys/fs/binfmt_misc tell the causes apart; the fix is an explicit docker buildx build --platform (or a manifest list for both), QEMU registration or --platform when you mean to emulate, and a #!/bin/sh line for the script.
curl: (60) SSL certificate problem: unable to get local issuer certificate
curl walked the certificate chain the server sent and reached a certificate whose issuer is not in the CA store it is reading, so it refused the connection with exit code 60. The issuer is missing for one of four reasons: the server sends only its leaf certificate without the intermediate (browsers hide this by fetching it themselves), a TLS-inspecting proxy re-signed the site with a company CA that the container or runner does not trust, curl is reading a different CA bundle than you think (CURL_CA_BUNDLE, SSL_CERT_FILE, a vendored curl), or the ca-certificates package is too old. curl -v shows which store was used and openssl s_client shows what the server sent; fix the side that is missing the link and confirm with -w '%{ssl_verify_result}'.