Troubleshooting
Redis & caching

Fix Redis "READONLY You Can’t Write Against a Read Only Replica"

Fix the Redis error READONLY You can’t write against a read only replica in Django and Celery: point clients at the primary endpoint, handle failovers with Sentinel or managed endpoints, and stop stale DNS and connection pools from writing to replicas.

Problem statement

After a Redis failover, a maintenance event, or a configuration change, writes start failing:

redis.exceptions.ReadOnlyError: You can't write against a read only replica.

Cache sets, session saves, Celery task publishing, and rate-limit counters all fail, while reads may keep working. The client is connected to a Redis node that is now a replica. Replicas accept reads but reject writes. Either the app was configured with a replica address, or a failover promoted another node and the app's existing connections, or its cached DNS answer, still point at the old primary, which was demoted.

Why writes failApp connectedto node AFailoverA becomes replicaOld connectionsstill go to AWrites rejectedREADONLY errorClients must follow the primary role, not a fixed node address.
  1. Check the node's role: redis-cli -h <host> ROLE shows master or slave.
  2. Point Django and Celery at the primary endpoint your provider gives, not a node or reader endpoint.
  3. With Sentinel, use the Sentinel-aware client configuration so the app asks Sentinel for the current primary.
  4. Lower DNS caching and reconnect on ReadOnlyError so pools drop stale connections.
  5. On self-managed setups, make sure replica-read-only yes replicas are never in the app's config.

Step-by-step solution

1. Check which node the app talks to

redis-cli -h redis.internal -p 6379 ROLE
redis-cli -h redis.internal -p 6379 INFO replication | head -5

If the address in CACHES or CELERY_BROKER_URL answers slave, the app is pointed at a replica by configuration. If it answers master now but the errors happened earlier, a failover occurred and connections were stale.

2. Use the primary endpoint on managed Redis

Managed services give a stable name that follows the primary:

  • ElastiCache (cluster mode disabled): the Primary endpoint, not the Reader endpoint or a node endpoint.
  • Azure Cache for Redis: the main host name.
  • Memorystore: the primary IP; the read endpoint is separate.
REDIS_URL = os.environ["REDIS_URL"]  # rediss://master.myredis.abc123.use1.cache.amazonaws.com:6379
CACHES = {"default": {"BACKEND": "django_redis.cache.RedisCache", "LOCATION": f"{REDIS_URL}/1"}}
CELERY_BROKER_URL = f"{REDIS_URL}/0"
endpoint types on managed RedisFollows failoverUse for writesPrimary endpointyesyesReader endpointyes, to replicas!noNode endpoint!no!noSentinelclient asksyes, via clientOnly role-following endpoints keep writes on the primarydjango-deployment.com
Node endpoints work until the first failover, which is why the error appears suddenly.

3. Configure Sentinel properly

With self-managed Sentinel, clients must ask Sentinel which node is primary. django-redis supports it:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://mymaster/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.SentinelClient",
            "SENTINELS": [("sentinel-1", 26379), ("sentinel-2", 26379), ("sentinel-3", 26379)],
            "CONNECTION_POOL_CLASS": "redis.sentinel.SentinelConnectionPool",
        },
    }
}

For Celery, use the sentinel:// broker URL with broker_transport_options = {"master_name": "mymaster"}.

4. Drop stale connections after failover

Even with the right endpoint, pooled connections opened before a failover remain attached to the old node. redis-py's Sentinel pool reconnects on ReadOnlyError. For plain connections, short SOCKET_TIMEOUT, health_check_interval, and a retry policy help:

"OPTIONS": {
    "SOCKET_TIMEOUT": 2,
    "CONNECTION_POOL_KWARGS": {"health_check_interval": 30, "retry_on_timeout": True},
}

Restarting web and worker processes after a failover clears pools if errors persist. Also keep DNS caching short: Java-style long caches do not apply to Python, but container platforms and local resolvers such as nscd can cache endpoints for minutes.

5. Self-managed replicas

If you run replicas yourself without Sentinel, a replica that was a primary before a manual switch keeps rejecting writes. Make sure the app config changes in the same step as the role change, ideally by using a DNS name or virtual IP you move with the primary.

primary endpoint, stale poolPrimary A failsor maintenanceReplica B promotedendpoint updatedA rejoins as replicaread onlyOld pool writes to AREADONLY errorsPool reconnectswrites go to BErrors last until stale connections are replaceddjango-deployment.com
Health checks and retries shorten the window to a few seconds.

Explanation

Replication in Redis is asynchronous and one-directional. Replicas are read-only by default (replica-read-only yes) so they cannot diverge from the primary. A failover changes which node is primary but cannot change where existing TCP connections point. Any client holding a connection to the old primary, now a replica, gets READONLY on its next write. Clients that discover the primary through Sentinel, Cluster, or a provider endpoint recover by reconnecting.

Edge cases / notes

  • Redis Cluster. In cluster mode, clients get MOVED redirects instead, and need a cluster-aware client. Celery's Redis transport does not support Cluster mode.
  • Read replicas for cache reads. django-redis supports a primary and replica list for reads; writes always go to the first entry, which must be the primary endpoint.
  • Maintenance windows. Managed providers fail over during patching. Expect this error once in a while unless clients handle it.

Next steps

FAQ

Why do reads work but writes fail?

Replicas serve reads normally. Only write commands are rejected with READONLY.

Do I need Sentinel on ElastiCache?

No. ElastiCache handles failover and updates the primary endpoint. Use that endpoint instead of Sentinel.

Will restarting Django fix it?

Restarting clears stale connections, so it often does. The lasting fix is a correct endpoint and reconnect behavior.

Can Celery use Redis Cluster?

Not with the standard Redis transport. Use a single primary with replicas and failover, or a different broker.

Keep going