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 value

Or 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 refused

Each 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 value

Redis 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 session as a string, service B tries to use session as 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:1

This returns one of: string, list, set, zset, hash, stream, or none (if the key doesn’t exist).

Now you know which commands to use:

TypeUse these commands
stringGET, SET, INCR, APPEND
listLPUSH, RPUSH, LRANGE, LPOP
setSADD, SMEMBERS, SISMEMBER
zsetZADD, ZRANGE, ZSCORE, ZRANK
hashHSET, HGET, HGETALL, HDEL
streamXADD, 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:1

Then create it with the correct type:

HSET user:1 name "Alice" email "alice@example.com"

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:1

Fix 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:pending

This 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": "alice@example.com"})
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 dir in redis.conf).

Fix 1: Free Up Disk Space

Check available disk space:

df -h

If the filesystem containing the Redis data directory is full, free up space. Find the Redis data directory:

redis-cli CONFIG GET dir

This 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 BGSAVE

Check if it succeeded:

redis-cli LASTSAVE

Fix 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/redis

If Redis is writing to a custom directory, update the ownership to match. Check the running user:

ps aux | grep redis-server

Fix 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 no

This 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 yes

To make this change permanent, add it to redis.conf:

stop-writes-on-bgsave-error yes

Fix 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_memory

If it’s 0 (the default), the kernel may deny the fork. Set it to 1:

sudo sysctl vm.overcommit_memory=1

Make it permanent:

echo "vm.overcommit_memory=1" | sudo tee -a /etc/sysctl.conf

Check the Redis logs for fork errors:

sudo tail -50 /var/log/redis/redis-server.log

Look 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 memory

Key values to look at:

used_memory_human: 1.02G
maxmemory_human: 1.00G
maxmemory_policy: noeviction

Here, 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 2gb

Make it permanent in redis.conf:

maxmemory 2gb

Check your system’s available memory before increasing:

free -h

Don’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-lru

Available policies:

PolicyBehavior
noevictionDon’t evict anything. Return errors on writes. (default)
allkeys-lruEvict least recently used keys. Best for most caches.
allkeys-lfuEvict least frequently used keys.
allkeys-randomEvict random keys.
volatile-lruEvict LRU keys that have an expiry set.
volatile-lfuEvict LFU keys that have an expiry set.
volatile-randomEvict random keys that have an expiry set.
volatile-ttlEvict 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 --bigkeys

This scans the keyspace and reports the largest keys by type. Delete keys you don’t need:

redis-cli DEL large-unused-key

For a more detailed memory analysis:

redis-cli MEMORY USAGE mykey

This returns the exact bytes used by a specific key.

Check if keys have TTLs set:

redis-cli TTL mykey

Returns -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 3600

Connection Refused

Why This Happens

Could not connect to Redis at 127.0.0.1:6379: Connection refused

This 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-mode is blocking the connection.
  • A firewall is blocking port 6379.

Fix 1: Start Redis

Linux (systemd):

sudo systemctl start redis

Check status:

sudo systemctl status redis

The service name varies by distribution. It might be redis-server, redis, or redis-server.service. Check with:

sudo systemctl list-units | grep redis

macOS (Homebrew):

brew services start redis

Docker:

docker run -d --name redis -p 6379:6379 redis:7

Fix 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 port

Or check redis.conf:

grep ^bind /etc/redis/redis.conf
grep ^port /etc/redis/redis.conf

To accept connections from all interfaces:

bind 0.0.0.0

Or for specific interfaces:

bind 127.0.0.1 192.168.1.100

Restart Redis after changing redis.conf:

sudo systemctl restart redis

Fix 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:6379

For development only, you can disable protected mode:

redis-cli CONFIG SET protected-mode no

Never 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:
      - redis

The 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.1

For 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 replication

Look for:

role:slave
master_host:192.168.1.10
master_port:6379

Connect 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 no

Data 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 deserialize or invalid character errors.
  • Getting b'\x80\x04\x95...' instead of your data (Python pickle bytes).
  • SyntaxError: Unexpected token when 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 needed

Key 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 expired

Don’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-history

Common causes:

  • Slow commands. KEYS * scans the entire keyspace. Use SCAN instead. 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 everysec instead 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/enabled

AUTH 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.conf

Pass the password when connecting:

redis-cli -a "your-password"
# Or after connecting:
redis-cli
> AUTH your-password

In Redis 6.0+ with ACLs, you may need a username too:

redis-cli --user myuser --pass mypassword

Redis Won’t Start After Config Change

Check the logs:

sudo tail -50 /var/log/redis/redis-server.log
# or
sudo journalctl -u redis -n 50

Common config mistakes:

  • Invalid maxmemory value. Use 1gb, 512mb, or bytes. Not 1 GB or 1g.
  • bind without quotes. It should be bind 127.0.0.1, not bind "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 0

Environment 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/0

The /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