This error occurs when your application tries to write to a Redis read-only replica instead of the primary master node. Redis replicas are intentionally set to read-only by default to prevent accidental writes and data inconsistency.
In Redis, replication creates a master-replica topology where the master accepts both read and write operations, while replicas (also called slaves) only accept read operations. Since Redis 2.6, replicas are configured with replica-read-only enabled by default to protect against accidental writes that would cause data inconsistency. When your application sends a write command (SET, DEL, INCR, etc.) to a replica, Redis immediately rejects it with the READONLY error because replicas are designed to be read-only copies of the master data.
Connect to your Redis instance and run the INFO REPLICATION command to see the current role. If the output shows "role:slave" or "role:replica", you are connected to a read-only replica, not the master.
redis-cli -h <your-redis-host> INFO REPLICATIONLook for the line starting with "role:". It should show "role:master" if you're connected to the primary node. If it shows "role:slave" or "role:replica", that explains the error.
Check your application's Redis configuration. In cloud-managed Redis (AWS ElastiCache, Google Cloud Memorystore, Azure Cache for Redis), there are separate primary and replica endpoints. Ensure you're using the primary/master endpoint, not a replica endpoint.
For example, in a Node.js application:
const redis = require("redis");
const client = redis.createClient({
host: "myredis-primary.abc123.ng.0001.use1.cache.amazonaws.com", // Correct: primary endpoint
port: 6379,
});Common mistake:
// Wrong: using node endpoint instead of cluster endpoint
const client = redis.createClient({
host: "myredis-node-001.abc123.ng.0001.use1.cache.amazonaws.com",
port: 6379,
});If your application uses DNS to resolve the Redis host, DNS caching can route old queries to the previous primary node (which is now a replica). Clear DNS caches or reduce TTL:
# On Linux, flush systemd DNS cache
sudo resolvectl flush-caches
# Or use dnsmasq
sudo systemctl restart dnsmasqIn your application, consider using connection pooling with automatic failover detection. For example, in redis-py:
from redis.sentinel import Sentinel
sentinel = Sentinel([(host, 26379) for host in sentinel_hosts])
redis_conn = sentinel.master_for("mymaster", socket_connect_timeout=0.2)After a failover event, the primary endpoint may change. Update your application configuration to connect to the new primary node:
# Query your infrastructure for the new primary endpoint
# AWS ElastiCache example:
aws elasticache describe-replication-groups --replication-group-id myredis
# Look for "PrimaryEndpoint" in the responseOr use Redis Sentinel/Cluster for automatic failover handling:
const redis = require("redis");
const client = redis.createClient({
sentinels: [
{ host: "sentinel1", port: 26379 },
{ host: "sentinel2", port: 26379 },
],
name: "mymaster", // The logical name of the master set
});If you manage your own Redis instance, check the replica-read-only configuration setting. By default (since Redis 2.6), it should be enabled for security:
redis-cli CONFIG GET replica-read-only
# Returns: "replica-read-only" "yes"Only disable read-only replicas if you have a specific use case and understand the consistency implications. Changing this at runtime:
redis-cli CONFIG SET replica-read-only noTo persist it, edit redis.conf:
replica-read-only noNote: Disabling read-only mode is NOT recommended for standard setups as it can lead to data divergence between master and replica.
Implement retry logic in your application to detect when you've been routed to a replica and handle it gracefully:
const redis = require("redis");
const { promisify } = require("util");
const client = redis.createClient();
const infoAsync = promisify(client.info).bind(client);
async function setWithFailover(key: string, value: string, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
// Attempt the write
return await promisify(client.set).bind(client)(key, value);
} catch (err) {
if (err.message.includes("READONLY")) {
// Reconnect to primary endpoint
console.warn("Connected to replica, reconnecting to primary...");
// Reload configuration or reconnect client
continue;
}
throw err;
}
}
}In Redis Cluster mode, the READONLY command is used differently. When connecting to a cluster replica (not to be confused with read-only replicas in non-cluster mode), you must issue the READONLY command before reading to tell the replica node you accept stale data. However, write operations are never allowed on any replica in cluster mode—writes must always go to the slot owner (master). For managed services like AWS ElastiCache with cluster mode enabled, the primary endpoint automatically routes to the appropriate shard master.
For high-availability setups using Redis Sentinel, connections should go through Sentinel (usually on port 26379) rather than directly to Redis nodes. Sentinel monitors the topology and provides the current primary endpoint to clients.
Asynchronous replication means the primary acknowledges writes before replicating to replicas, so brief data loss is possible if the primary crashes immediately after acknowledging a write but before replication completes. This is a design tradeoff for performance.
ERR fsync error
How to fix "ERR fsync error" in Redis
CLUSTERDOWN The cluster is down
How to fix 'CLUSTERDOWN The cluster is down' in Redis
ERR Job for redis-server.service failed because a timeout was exceeded
Job for redis-server.service failed because a timeout was exceeded
ERR Unbalanced XREAD list of streams
How to fix "ERR Unbalanced XREAD list" in Redis
ERR syntax error
How to fix "ERR syntax error" in Redis