redis/redis-py · error · ConnectionError

Connection closed by server.

Error message

Connection closed by server.

What it means

Raised by the sync RESP3 parser _read_response when SocketBuffer.readline() returns empty: the server closed the socket (recv returned b''). RESP3 (protocol=3, the current default on the wire) path's 'Connection closed by server.' and the RESP3 equivalent of errors 28/36.

Solutions

  1. Configure retry_on_error=[ConnectionError, TimeoutError] + Retry/backoff.
  2. Enable health_check_interval and socket_keepalive.
  3. Set socket_timeout larger than any blocking-command block time.
  4. Catch redis.exceptions.ConnectionError and reconnect/retry.

Example fix

# before
r = redis.Redis()  # protocol=3 is the default; no retry
r.get('k')  # after a restart -> ConnectionError: Connection closed by server.

# after
from redis.retry import Retry
from redis.backoff import ExponentialWithJitterBackoff
from redis.exceptions import ConnectionError, TimeoutError
r = redis.Redis(
    health_check_interval=30, socket_keepalive=True, socket_timeout=5,
    retry_on_error=[ConnectionError, TimeoutError],
    retry=Retry(ExponentialWithJitterBackoff(), 3),
)
r.get('k')
Defensive patterns

Strategy: retry

Validate before calling

from redis.exceptions import ConnectionError

def alive(r):
    try:
        r.ping()
        return True
    except ConnectionError:
        return False

Type guard

from redis.exceptions import ConnectionError as RCE

def resp3_server_closed(e: BaseException) -> bool:
    return isinstance(e, RCE) and 'closed by server' in str(e).lower()

Try / catch

from redis.exceptions import ConnectionError, TimeoutError
try:
    r.get('k')
except (ConnectionError, TimeoutError):
    r.get('k')

Prevention

When it happens

Trigger: Any sync command over a protocol=3 connection whose reply is interrupted by a server-side close: restart, failover, CLIENT KILL, idle timeout, maxmemory-clients eviction, network drop.

Common situations: Default RESP3 client on a long-lived connection behind an aggressive idle reaper; failover of a managed Redis; RDB save under memory pressure dropping clients; a misconfigured 'timeout' in redis.conf.

Understand the failure class

Related errors


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

Appendix: source

Thrown at redis/_parsers/resp3.py:67

        else:
            if self._buffer is not None:
                try:
                    self._buffer.purge()
                except AttributeError:
                    # Buffer may have been set to None by another thread after
                    # the check above; result is still valid so we don't raise
                    pass
            return result

    def _read_response(
        self,
        disable_decoding=False,
        push_request=False,
        timeout: Union[float, object] = SENTINEL,
    ):
        raw = self._buffer.readline(timeout=timeout)
        if not raw:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)

        byte, response = raw[:1], raw[1:]

        # server returned an error
        if byte in (b"-", b"!"):
            if byte == b"!":
                response = self._buffer.read(int(response), timeout=timeout)
            response = response.decode("utf-8", errors="replace")
            error = self.parse_error(response)
            # if the error is a ConnectionError, raise immediately so the user
            # is notified
            if isinstance(error, ConnectionError):
                raise error
            # otherwise, we're dealing with a ResponseError that might belong
            # inside a pipeline response. the connection's read_response()
            # and/or the pipeline's execute() will raise this error if
            # necessary, so just return the exception instance here.
            return error

View on GitHub (pinned to 6a6b581b48)