READONLY You can't write against a read only replica is the reply Redis gives when a write command reaches a replica. By default replicas accept reads only, so the server refuses the write. In almost every incident the database is fine. The problem is that the application is connected to the wrong node, usually right after a failover.
The Redis error reference puts it simply: only the primary accepts writes. Valkey, which forked from Redis, behaves the same way and uses the same message. So the question is not "what is wrong with Redis", but "why does my client think this replica is the primary?"
First, check what the node really is
$ redis-cli -h cache-01 SET k v
(error) READONLY You can't write against a read only replica.
$ redis-cli -h cache-01 ROLE
1) "slave"
2) "10.0.2.15" # the primary this node follows
3) (integer) 6379
4) "connected"
5) (integer) 3167038
$ redis-cli -h cache-01 INFO replication | head -5
# Replication
role:slave
master_host:10.0.2.15
master_port:6379
master_link_status:up
ROLE returns slave as the first element on a replica (the word stays in the protocol for backward compatibility), followed by the primary's address, the link state and the replication offset. INFO replication gives the same picture in text form. If the node is a replica, look at which primary it follows. That tells you where the writes should have gone.
The usual causes
- A failover happened and the client did not notice. The old primary comes back as a replica of the new one. Any client still holding connections to the old address now gets READONLY on every write. This is the most common case by far.
- The client was configured with a node address, not a service address. A hard-coded IP or node hostname works until the roles change. With ElastiCache, AWS documents that during failover it propagates the DNS name of the promoted replica to the primary endpoint, so applications writing to the primary endpoint need no change. Applications that write to an individual node endpoint do.
- DNS or connection caching. Even with the right endpoint, a client or runtime that caches DNS results, or a connection pool that never reconnects, keeps talking to the old node after the endpoint has moved.
- A load balancer or Kubernetes Service that includes replicas. If one Service or VIP points at all Redis pods, some share of the writes lands on replicas. The error then appears randomly, not just after a failover.
- Somebody demoted the node. A manual
REPLICAOF, an automation script, or Sentinel reconfiguring a node it considered stale can all turn a primary into a replica. - Read-only routing in the client. Clients that split reads and writes can send a write down the read path, for example a Lua script or
SORT ... STOREthat the client treats as a read.
Scripts and "read" commands that are really writes
The error reference notes that a Lua function, or a script starting with a #!lua line, gets READONLY on a replica before it starts unless it has the no-writes flag. A plain script gets the error when it calls a write command. The replication documentation also points out that EVAL, EVALSHA and SORT are not read-only commands. If you want to run them on replicas, use EVAL_RO, EVALSHA_RO and SORT_RO, and replace SUNIONSTORE or ZINTERSTORE into temporary keys with SUNION or ZINTER.
Fix the client, not the replica
- With Sentinel, let the client discover the primary. The Sentinel client specification describes the procedure: ask a Sentinel with
SENTINEL get-master-addr-by-name, connect, and confirm withROLEthat the node really is a master. On every reconnection the client should ask Sentinel again. When Sentinel reconfigures an instance it sendsCLIENT KILL type normal, which forces clients to reconnect and resolve the address again. A client that reconnects to a cached address defeats this. - On ElastiCache, write only to the primary endpoint (or the configuration endpoint in cluster mode), and make sure the runtime does not cache DNS for longer than the failover window.
- Treat READONLY as a reconnect signal. Many clients can be configured to drop the connection and rediscover the primary when they see it. Retrying the same write on the same connection will fail every time.
- Separate write and read endpoints. In Kubernetes, give writes a Service that only selects the current primary. Sentinel-aware clients or an operator that labels the primary handle this. A Service over all pods does not.
# ask Sentinel who the primary is right now
$ redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
1) "10.0.2.21"
2) "6379"
# then verify on that node before trusting it
$ redis-cli -h 10.0.2.21 ROLE
1) "master"
...
Related errors that look similar
MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no': the replica has lost its primary and is configured to refuse data commands instead of serving stale data.NOREPLICAS Not enough good replicas to write: you are on the primary, butmin-replicas-to-writeis set and not enough replicas are connected with acceptable lag. By defaultmin-replicas-to-writeis 0, so this only appears when someone enabled it.LOADING Redis is loading the dataset in memory: the node is restarting or finishing a full sync. We covered it in Redis LOADING dataset in memory.
What changes with large datasets
With tens of gigabytes in memory, the minutes after a failover are where READONLY turns into a longer outage:
- The old primary may need a full resync. Since Redis 4.0 a promoted replica can still serve partial resynchronization to the old primary's replicas, but only if the replication backlog still covers the gap. Redis defaults
repl-backlog-sizeto 1 MB (current Valkey source uses 10 MB), which a busy primary fills in seconds. Size it to your write rate and the outage you want to survive. - Full syncs cost memory and time. The primary forks to produce the snapshot, and the default
client-output-buffer-limit replica 256mb 64mb 60can disconnect a replica that falls behind during a long transfer, which restarts the sync. On large datasets, check that limit before a planned failover. - Replicas block while loading. The replication documentation notes that after the initial sync the replica must load the new dataset in the main thread and blocks connections during that window, which can be many seconds for very large datasets. Clients that fall back to a replica at that moment get LOADING instead of READONLY.
- Reconnect storms. Thousands of clients rediscovering the primary at once add load on Sentinels and on the new primary. Use backoff with jitter in reconnection logic.
- Test failover deliberately. On ElastiCache, the
test-failoverAPI exists for exactly this. With Sentinel,SENTINEL FAILOVERdoes the same. Watch how long your applications keep writing to the old node. That number is your real failover time, not the seconds the database needs.
The same rules apply if you have moved or plan to move to Valkey, see our Redis to Valkey migration guide: replicas are read-only by default and clients must follow the primary.
Key takeaways
- READONLY means a write reached a replica; the database is usually healthy.
- Check the node with
ROLEorINFO replicationand see which primary it follows. - The common cause is a client still connected to the old primary after a failover, often through a node address or cached DNS.
- Fix discovery: Sentinel-aware clients, the ElastiCache primary endpoint, a write-only Service in Kubernetes.
- Never set
replica-read-only noas a workaround; on large datasets also size the backlog and output buffers so failovers do not end in full resyncs.