redis/redis-py · error · ConnectionError

READONLY command failed

Error message

READONLY command failed

What it means

Raised as a ConnectionError during the cluster connection pool's on_connect hook when the READONLY command (sent to enable replica reads) does not return the expected 'OK' response. The client sends READONLY on every new connection when read_from_replicas or load_balancing_strategy is configured, so a non-OK reply means the server rejected or could not process the command. This prevents the client from silently using connections that cannot serve replica reads.

Solutions

  1. Verify the target is actually a Redis Cluster deployment (not a standalone Redis) by running CLUSTER INFO on the node.
  2. If you do not need replica reads, remove read_from_replicas=True and load_balancing_strategy from your RedisCluster constructor so the client stops sending READONLY.
  3. Check Redis server ACL/permissions to ensure the authenticated user is allowed to run the READONLY command (check aclfile or ACL LIST).
  4. Upgrade the Redis server to a version that supports cluster READONLY semantics if you are on an old release.

Example fix

// before
client = RedisCluster(host='localhost', port=7000, read_from_replicas=True)

// after (replica reads not needed)
client = RedisCluster(host='localhost', port=7000)
Defensive patterns

Strategy: validation

Validate before calling

# Validate cluster mode and READONLY support before constructing with read_from_replicas
import redis
info = redis.Redis(host, port).cluster('INFO')  # raises if not cluster
if 'cluster_enabled:1' not in info:
    raise RuntimeError('Not a cluster node; cannot use read_from_replicas')

Type guard

def supports_readonly(conn: redis.Redis) -> bool:
    try:
        return conn.execute_command('COMMAND', 'INFO', 'READONLY') is not None
    except redis.ResponseError:
        return False

Try / catch

try:
    client = RedisCluster(host, port, read_from_replicas=True)
except ConnectionError as e:
    if 'READONLY command failed' in str(e):
        # fall back without replica reads
        client = RedisCluster(host, port)

Prevention

When it happens

Trigger: Constructing RedisCluster with read_from_replicas=True or a load_balancing_strategy, then issuing any command that opens a connection to a node whose server does not return 'OK' to the READONLY command. This is triggered inside PerthConnectionPool.on_connect (cluster.py:1011-1026) on every new connection.

Common situations: Connecting a RedisCluster client to a standalone (non-cluster) Redis instance, pointing at a Redis version that does not support READONLY in the expected form, hitting an authentication/AACL issue where the READONLY command is denied, or a transient server error during failover that causes the server to reject READONLY.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/313e23c2a41db5bc. Report an issue: GitHub.

Appendix: source

Thrown at redis/cluster.py:1026

                    # Client was already disconnected. do nothing
                    pass

    def on_connect(self, connection):
        """
        Initialize the connection, authenticate and select a database and send
         READONLY if it is set during object initialization.
        """
        connection.on_connect()

        if self.read_from_replicas or self.load_balancing_strategy:
            # Sending READONLY command to server to configure connection as
            # readonly. Since each cluster node may change its server type due
            # to a failover, we should establish a READONLY connection
            # regardless of the server type. If this is a primary connection,
            # READONLY would not affect executing write commands.
            connection.send_command("READONLY")
            if str_if_bytes(connection.read_response()) != "OK":
                raise ConnectionError("READONLY command failed")

        if self.user_on_connect_func is not None:
            self.user_on_connect_func(connection)

    def get_redis_connection(self, node: "ClusterNode") -> Redis:
        if not node.redis_connection:
            with self._lock:
                if not node.redis_connection:
                    self.nodes_manager.create_redis_connections([node])
        return node.redis_connection

    def get_node(self, host=None, port=None, node_name=None):
        return self.nodes_manager.get_node(host, port, node_name)

    def get_primaries(self):
        return self.nodes_manager.get_nodes_by_server_type(PRIMARY)

    def get_replicas(self):

View on GitHub (pinned to 6a6b581b48)