Fix: WRONGTYPE Operation against a key holding the wrong kind of value (Redis)
The Error
You run a Redis command and get:
WRONGTYPE Operation against a key holding the wrong kind of valueOr one of these other common Redis errors:
MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk.OOM command not allowed when used memory > 'maxmemory'.READONLY You can't write against a read only replica.Could not connect to Redis at 127.0.0.1:6379: Connection refusedEach of these has a different root cause. This guide covers all of them.
WRONGTYPE Operation Against a Key Holding the Wrong Kind of Value
Why This Happens
Redis stores data in different structures: strings, lists, sets, sorted sets, hashes, and streams. Each structure has its own set of commands. You get WRONGTYPE when you use a command meant for one data type on a key that holds a different type.
For example, you create a string key:
SET user:1 "Alice"Then try to use a list command on it:
LPUSH user:1 "Bob"
# WRONGTYPE Operation against a key holding the wrong kind of valueRedis won’t auto-convert between types. The key user:1 is a string, and LPUSH only works on lists.
This commonly happens when:
- Your application reuses a key name for a different purpose. A key was originally a string, but new code treats it as a hash or list.
- Multiple services share a Redis instance without key namespacing. Service A stores
sessionas a string, service B tries to usesessionas a hash. - A cache key was populated by an older version of your code that used a different data structure.
- You’re mixing up Redis client library methods. Some libraries abstract away the underlying Redis commands, making it easy to use the wrong one.
Fix 1: Check the Key Type
Before doing anything else, check what type the key actually holds:
TYPE user:1This returns one of: string, list, set, zset, hash, stream, or none (if the key doesn’t exist).
Now you know which commands to use:
| Type | Use these commands |
|---|---|
string | GET, SET, INCR, APPEND |
list | LPUSH, RPUSH, LRANGE, LPOP |
set | SADD, SMEMBERS, SISMEMBER |
zset | ZADD, ZRANGE, ZSCORE, ZRANK |
hash | HSET, HGET, HGETALL, HDEL |
stream | XADD, XREAD, XRANGE |
Fix 2: Delete and Recreate the Key
If the key holds stale data or was created with the wrong type, delete it and start fresh:
DEL user:1Then create it with the correct type:
HSET user:1 name "Alice" email "[email protected]"If you need to preserve the data, read it first:
GET user:1
# "Alice"
DEL user:1
HSET user:1 name "Alice"Warning: DEL is blocking. On very large keys (millions of elements), use UNLINK instead. UNLINK removes the key from the keyspace immediately but reclaims memory in the background:
UNLINK user:1Fix 3: Use Key Naming Conventions
Prevent WRONGTYPE errors entirely by adopting a consistent naming scheme. Prefix or suffix keys with their purpose and data type:
user:1:profile → hash (user attributes)
user:1:sessions → set (active session IDs)
user:1:notifications → list (notification queue)
user:1:score → string (numeric value)Namespace by service or application:
auth:session:abc123
cache:product:42
queue:emails:pendingThis makes it obvious what type each key holds and prevents collisions between services sharing a Redis instance.
Fix 4: Fix Your Application Code
The real fix is usually in your code. Check where the key is created and where it’s read. Look for mismatched operations.
Python (redis-py):
import redis
r = redis.Redis()
# Wrong: mixing string and hash operations on the same key
r.set("user:1", "Alice")
r.hget("user:1", "name") # WRONGTYPE
# Right: pick one data type and stick with it
r.hset("user:1", mapping={"name": "Alice", "email": "[email protected]"})
r.hget("user:1", "name") # "Alice"Node.js (ioredis):
const Redis = require("ioredis");
const redis = new Redis();
// Wrong
await redis.set("user:1", "Alice");
await redis.hget("user:1", "name"); // WRONGTYPE
// Right
await redis.hset("user:1", "name", "Alice");
await redis.hget("user:1", "name"); // "Alice"Defensive pattern — check the type before operating:
key_type = r.type("user:1")
if key_type == b"hash":
r.hget("user:1", "name")
elif key_type == b"string":
r.get("user:1")
elif key_type == b"none":
# Key doesn't exist, safe to create as any type
r.hset("user:1", mapping={"name": "Alice"})MISCONF Redis Is Configured to Save RDB Snapshots
Why This Happens
You run a write command and Redis responds with:
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.Redis periodically saves a snapshot of your data to disk (RDB persistence). When this save fails, Redis stops accepting write commands to prevent data loss. The snapshot fails because:
- The disk is full. Redis can’t write the RDB file.
- Redis doesn’t have write permissions to the RDB directory.
- A background save fork failed due to insufficient memory (Redis forks the process to create the snapshot, which temporarily requires extra memory).
- The RDB directory doesn’t exist (misconfigured
dirinredis.conf).
Fix 1: Free Up Disk Space
Check available disk space:
df -hIf the filesystem containing the Redis data directory is full, free up space. Find the Redis data directory:
redis-cli CONFIG GET dirThis returns the directory where Redis writes the dump.rdb file. Clear unnecessary files on that filesystem, then trigger a save to verify it works:
redis-cli BGSAVECheck if it succeeded:
redis-cli LASTSAVEFix 2: Fix Directory Permissions
Redis needs write access to its data directory. Check who owns it:
ls -la /var/lib/redis/The directory should be owned by the redis user:
sudo chown redis:redis /var/lib/redis
sudo chmod 750 /var/lib/redisIf Redis is writing to a custom directory, update the ownership to match. Check the running user:
ps aux | grep redis-serverFix 3: Disable stop-writes-on-bgsave-error (Temporary)
If you need writes to work immediately while you fix the underlying issue:
redis-cli CONFIG SET stop-writes-on-bgsave-error noThis is a temporary fix. It lets Redis accept writes even when persistence is broken, which means you risk data loss if Redis restarts. Fix the underlying disk or permissions issue, then re-enable:
redis-cli CONFIG SET stop-writes-on-bgsave-error yesTo make this change permanent, add it to redis.conf:
stop-writes-on-bgsave-error yesFix 4: Fix Background Save Fork Failures
When Redis runs BGSAVE, it forks the process. On Linux, this requires enough memory for the fork (even though copy-on-write means it doesn’t actually double memory usage). If the system denies the fork, the save fails.
Check if overcommit_memory is the problem:
cat /proc/sys/vm/overcommit_memoryIf it’s 0 (the default), the kernel may deny the fork. Set it to 1:
sudo sysctl vm.overcommit_memory=1Make it permanent:
echo "vm.overcommit_memory=1" | sudo tee -a /etc/sysctl.confCheck the Redis logs for fork errors:
sudo tail -50 /var/log/redis/redis-server.logLook for messages like Can't save in background: fork: Cannot allocate memory.
OOM Command Not Allowed
Why This Happens
Redis responds with:
OOM command not allowed when used memory > 'maxmemory'.Redis has a configured memory limit (maxmemory), and it’s been reached. Redis is refusing new write commands because there’s no room and no eviction policy is configured (or all keys are non-evictable). If you’re running Redis in Docker, the container itself may be killed by the OOM killer before Redis even gets a chance to report this error.
Fix 1: Check Current Memory Usage
redis-cli INFO memoryKey values to look at:
used_memory_human: 1.02G
maxmemory_human: 1.00G
maxmemory_policy: noevictionHere, Redis is using 1.02 GB against a 1 GB limit with noeviction policy, meaning Redis won’t delete any keys to make room. It just rejects writes.
Fix 2: Increase maxmemory
If your server has available RAM:
redis-cli CONFIG SET maxmemory 2gbMake it permanent in redis.conf:
maxmemory 2gbCheck your system’s available memory before increasing:
free -hDon’t set maxmemory higher than your available RAM. Leave room for the OS, background save forks, and other processes.
Fix 3: Set an Eviction Policy
If Redis is used as a cache (where data loss is acceptable), configure an eviction policy so Redis automatically removes old keys when memory is full:
redis-cli CONFIG SET maxmemory-policy allkeys-lruAvailable policies:
| Policy | Behavior |
|---|---|
noeviction | Don’t evict anything. Return errors on writes. (default) |
allkeys-lru | Evict least recently used keys. Best for most caches. |
allkeys-lfu | Evict least frequently used keys. |
allkeys-random | Evict random keys. |
volatile-lru | Evict LRU keys that have an expiry set. |
volatile-lfu | Evict LFU keys that have an expiry set. |
volatile-random | Evict random keys that have an expiry set. |
volatile-ttl | Evict keys with the shortest TTL. |
For caching, use allkeys-lru. For sessions with TTLs, use volatile-ttl or volatile-lru.
Important: The volatile-* policies only evict keys that have a TTL set. If none of your keys have a TTL, Redis can’t evict anything and you’ll still get OOM errors. Use allkeys-* if your keys don’t have TTLs.
Fix 4: Clean Up Unnecessary Keys
Find large keys consuming memory:
redis-cli --bigkeysThis scans the keyspace and reports the largest keys by type. Delete keys you don’t need:
redis-cli DEL large-unused-keyFor a more detailed memory analysis:
redis-cli MEMORY USAGE mykeyThis returns the exact bytes used by a specific key.
Check if keys have TTLs set:
redis-cli TTL mykeyReturns -1 if the key has no expiry (it lives forever), -2 if the key doesn’t exist, or the remaining seconds until expiry.
Add TTLs to cache keys that don’t have them:
redis-cli EXPIRE cache:product:42 3600Connection Refused
Why This Happens
Could not connect to Redis at 127.0.0.1:6379: Connection refusedThis means nothing is listening on that address and port. Common causes:
- Redis isn’t running.
- Redis is listening on a different address or port.
protected-modeis blocking the connection.- A firewall is blocking port 6379.
Fix 1: Start Redis
Linux (systemd):
sudo systemctl start redisCheck status:
sudo systemctl status redisThe service name varies by distribution. It might be redis-server, redis, or redis-server.service. Check with:
sudo systemctl list-units | grep redismacOS (Homebrew):
brew services start redisDocker:
docker run -d --name redis -p 6379:6379 redis:7Fix 2: Check Bind Address and Port
Redis defaults to binding on 127.0.0.1 only. If you’re connecting from another machine or container, Redis won’t accept the connection.
Check the current configuration:
redis-cli CONFIG GET bind
redis-cli CONFIG GET portOr check redis.conf:
grep ^bind /etc/redis/redis.conf
grep ^port /etc/redis/redis.confTo accept connections from all interfaces:
bind 0.0.0.0Or for specific interfaces:
bind 127.0.0.1 192.168.1.100Restart Redis after changing redis.conf:
sudo systemctl restart redisFix 3: Disable Protected Mode (Development Only)
Redis 3.2+ enables protected-mode by default. When enabled, Redis only accepts connections from 127.0.0.1 and rejects external connections if no password is set.
You get this error:
DENIED Redis is running in protected mode because no password is set...The right fix is to set a password:
redis-cli CONFIG SET requirepass "your-strong-password"Then connect with:
redis-cli -a "your-strong-password"Or in your application connection string:
redis://:your-strong-password@redis-host:6379For development only, you can disable protected mode:
redis-cli CONFIG SET protected-mode noNever disable protected mode in production.
Fix 4: Fix Docker Networking
If your app is in a Docker container, localhost inside the container refers to the container itself, not the host.
Docker Compose — use the service name:
services:
redis:
image: redis:7
ports:
- "6379:6379"
app:
build: .
environment:
REDIS_URL: redis://redis:6379
depends_on:
- redisThe hostname is redis (the service name), not localhost.
Connecting to host Redis from a container:
# Docker Desktop (macOS/Windows)
docker run --rm redis:7 redis-cli -h host.docker.internal
# Linux
docker run --rm --network host redis:7 redis-cli -h 127.0.0.1For more Docker networking details, see Fix: Docker Permission Denied.
READONLY You Can’t Write Against a Read Only Replica
Why This Happens
READONLY You can't write against a read only replica.You’re sending write commands to a Redis replica (slave), not the primary (master). Replicas are read-only by default.
This happens when:
- Your application is connecting to the wrong Redis instance. You have a primary and one or more replicas, and your connection string points to a replica.
- A failover happened. The old primary became a replica, and your app still connects to it.
- Redis Sentinel or Cluster hasn’t been configured in your client. Your client connects to a fixed address instead of discovering the current primary.
Fix 1: Connect to the Primary
Check the role of the instance you’re connected to:
redis-cli INFO replicationLook for:
role:slave
master_host:192.168.1.10
master_port:6379Connect to the primary listed in master_host and master_port.
Fix 2: Use Redis Sentinel in Your Client
If you’re using Redis Sentinel for high availability, configure your client to use Sentinel discovery instead of a fixed address:
Python (redis-py):
from redis.sentinel import Sentinel
sentinel = Sentinel([
("sentinel-1", 26379),
("sentinel-2", 26379),
("sentinel-3", 26379),
])
# Get the current primary
master = sentinel.master_for("mymaster")
master.set("key", "value") # Always writes to the current primary
# Get a replica for reads
slave = sentinel.slave_for("mymaster")
slave.get("key")Node.js (ioredis):
const Redis = require("ioredis");
const redis = new Redis({
sentinels: [
{ host: "sentinel-1", port: 26379 },
{ host: "sentinel-2", port: 26379 },
{ host: "sentinel-3", port: 26379 },
],
name: "mymaster",
});Fix 3: Allow Writes on Replica (Rare)
In specific cases (like local caching on replicas), you can allow writes:
redis-cli CONFIG SET replica-read-only noData written to a replica is not replicated to the primary or other replicas, and it will be lost on the next sync. This is rarely what you want.
Serialization Issues
Why This Happens
Your application writes an object to Redis and reads back garbage, or you get deserialization errors. Redis stores everything as byte strings. When you store complex objects, your client library serializes them, and the format must match on read and write.
Common symptoms:
Could not deserializeorinvalid charactererrors.- Getting
b'\x80\x04\x95...'instead of your data (Python pickle bytes). SyntaxError: Unexpected tokenwhen parsing JSON from Redis in Node.js (see Fix: JSON Parse Unexpected Token for more on this).
Fix: Be Explicit About Serialization
Store JSON, not language-specific formats:
import json
import redis
r = redis.Redis()
# Write
user = {"name": "Alice", "age": 30}
r.set("user:1", json.dumps(user))
# Read
data = json.loads(r.get("user:1"))Don’t mix serialization formats. If one service writes pickle and another reads JSON, it won’t work. Standardize on JSON for interoperability.
Use Redis hashes instead of serialized objects when you need to read or update individual fields:
r.hset("user:1", mapping={"name": "Alice", "age": "30"})
name = r.hget("user:1", "name") # No deserialization neededKey Expiry Patterns
Keys Not Expiring
You set a TTL on a key, but it seems to live forever:
SET mykey "value"
EXPIRE mykey 3600
# Later...
SET mykey "newvalue" # This REMOVES the TTL!
TTL mykey # Returns -1 (no expiry)Calling SET on a key that already has a TTL removes the TTL unless you explicitly include it:
SET mykey "newvalue" EX 3600 # Keeps the 1-hour TTL
SET mykey "newvalue" KEEPTTL # Preserves the existing TTL (Redis 6.0+)This is one of the most common Redis gotchas. Your cache keys never expire because every update resets them to permanent.
Expired Keys Still Showing Up
Redis uses lazy expiration and periodic sampling. A key might still appear in KEYS * or SCAN output briefly after expiring. Accessing the key triggers immediate deletion:
EXISTS mykey # Returns 0 if expired
GET mykey # Returns nil if expiredDon’t rely on DBSIZE or KEYS for exact counts of live keys. Use SCAN with your application logic to filter.
Still Not Working?
Redis Latency Spikes
If Redis commands are intermittently slow:
redis-cli --latency
redis-cli --latency-historyCommon causes:
- Slow commands.
KEYS *scans the entire keyspace. UseSCANinstead. Check slow queries:
redis-cli SLOWLOG GET 10- Large values. Storing megabytes in a single key blocks Redis (it’s single-threaded). Break large values into smaller keys.
- Background save (BGSAVE) on a busy server. The fork can cause latency spikes. Consider using AOF persistence with
appendfsync everysecinstead of RDB snapshots. - Transparent Huge Pages (THP). Linux THP causes latency spikes during fork. Disable it:
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabledAUTH Required but Not Provided
NOAUTH Authentication required.Redis requires a password but your client isn’t sending one. Check if auth is enabled:
grep ^requirepass /etc/redis/redis.confPass the password when connecting:
redis-cli -a "your-password"
# Or after connecting:
redis-cli
> AUTH your-passwordIn Redis 6.0+ with ACLs, you may need a username too:
redis-cli --user myuser --pass mypasswordRedis Won’t Start After Config Change
Check the logs:
sudo tail -50 /var/log/redis/redis-server.log
# or
sudo journalctl -u redis -n 50Common config mistakes:
- Invalid maxmemory value. Use
1gb,512mb, or bytes. Not1 GBor1g. - bind without quotes. It should be
bind 127.0.0.1, notbind "127.0.0.1". - Typo in config directive. Redis silently ignores unknown directives. Check spelling.
Validate the config file:
redis-server /etc/redis/redis.conf --test-memory 0Environment Variable Misconfiguration
If your app connects to Redis using environment variables and gets connection errors, the REDIS_URL or REDIS_HOST might not be set. Check Fix: Environment Variable Is Undefined for debugging this. Also make sure the URL format is correct:
redis://username:password@hostname:6379/0The /0 at the end is the database number (0-15 by default).
Database Connectivity Patterns
Redis connection issues often overlap with general database troubleshooting. If you’re also dealing with PostgreSQL, see Fix: PostgreSQL Connection Refused. For MySQL auth problems, see Fix: MySQL Access Denied for User.
Related: If your app can’t read connection details from environment variables, see Fix: Environment Variable Is Undefined. For Docker container networking issues, see Fix: Docker Permission Denied. For other database connection problems, see Fix: PostgreSQL Connection Refused and Fix: MySQL Access Denied for User.
Related Articles
Fix: Redis ECONNREFUSED – Connection Refused on localhost:6379
How to fix the Redis ECONNREFUSED error when your application cannot connect to the Redis server on localhost:6379 or a remote host. Covers Redis not running, wrong host/port, Docker networking, firewall, AUTH required, maxclients, and TLS configuration.
Fix: MongoServerError: bad auth / MongoNetworkError: connect ECONNREFUSED / MongooseServerSelectionError
How to fix MongoDB 'MongoServerError: bad auth Authentication failed', 'MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017', and 'MongooseServerSelectionError' connection errors. Covers MongoDB not running, connection string format, Atlas network access, Docker networking, authentication, DNS/SRV issues, TLS/SSL, and Mongoose options.
Fix: Access denied for user 'root'@'localhost' (MySQL ERROR 1045)
How to fix MySQL 'ERROR 1045 (28000): Access denied for user root@localhost (using password: YES/NO)' on Linux, macOS, Windows, and Docker. Covers password reset, auth plugin issues, skip-grant-tables recovery, MySQL 8 vs 5.7 differences, and host mismatches.
Fix: psql: error: connection to server refused (PostgreSQL)
How to fix 'could not connect to server: Connection refused', 'psql: error: connection to server at localhost port 5432 failed', and other PostgreSQL connection refused errors on Linux, macOS, Windows, and Docker.