redis/redis-py · error · ConnectionError
Bad response from PING health check
Error message
Bad response from PING health check
What it means
Raised as a ConnectionError by _send_ping() during check_health(): a PING was sent and a response came back, but it was not 'PONG'. This almost always means the response stream is desynchronized or something else (a proxy, a pub/sub message, a protocol parser bug) injected a non-PONG reply where the PONG was expected.
Source
Thrown at redis/asyncio/connection.py:1149
await record_connection_closed(
close_reason=CloseReason.APPLICATION_CLOSE,
)
if self.maintenance_state == MaintenanceState.MAINTENANCE:
# MOVING state is owned by the pool-level TTL cleanup. Regular
# maintenance timeout relaxation can be restored when this
# connection closes, matching the sync lifecycle.
self.reset_tmp_settings(reset_relaxed_timeout=True)
self.maintenance_state = MaintenanceState.NONE
# reset the sets that keep track of received start maint
# notifications and skipped end maint notifications
self.reset_received_notifications()
async def _send_ping(self):
"""Send PING, expect PONG in return"""
await self.send_command("PING", check_health=False)
if str_if_bytes(await self.read_response()) != "PONG":
raise ConnectionError("Bad response from PING health check")
async def _ping_failed(self, error, failure_count):
"""Function to call when PING fails"""
await self.disconnect(
error=error, failure_count=failure_count, health_check_failed=True
)
async def check_health(self):
"""Check the health of the connection with a PING/PONG"""
if (
self.health_check_interval
and asyncio.get_running_loop().time() > self.next_health_check
):
await self.retry.call_with_retry(
self._send_ping, self._ping_failed, with_failure_count=True
)
async def _send_packed_command(self, command: Iterable[bytes]) -> None:View on GitHub (pinned to da03cdc7e8)
Solutions
- Ensure only one coroutine uses a connection at a time (the pool enforces this; do not share a raw connection across tasks).
- Set health_check_interval=0 to disable health checks if the intermediary cannot pass PING/PONG cleanly, or remove the misbehaving proxy.
- Check for unread responses left over from prior commands (e.g. a command whose response was never read).
- Upgrade redis-py and the server; report a parser bug if it reproduces against a vanilla Redis.
Example fix
// before r = redis.asyncio.Redis(host=h, port=p, health_check_interval=30) # with a proxy that breaks PING // after r = redis.asyncio.Redis(host=h, port=p, health_check_interval=0)
Defensive patterns
Strategy: retry
Try / catch
from redis.exceptions import ConnectionError
for attempt in range(3):
try:
await r.get("k")
break
except ConnectionError as e:
if "PING health check" in str(e):
await asyncio.sleep(2 ** attempt)
continue
raise Prevention
- Never share one connection across concurrent coroutines.
- Disable health_check_interval if an intermediary corrupts PING/PONG.
- Always read every response you request to keep the stream in sync.
When it happens
Trigger: check_health() runs when health_check_interval is set and the connection is idle past next_health_check; _send_ping() reads via read_response() and the bytes are not b'PONG'. Also reachable if a proxy mangles PING/PONG, or if the connection was reused after a partial/errored prior command left unread bytes.
Common situations: A misbehaving proxy rewriting PING; pub/sub or push data (RESP3 out-of-band) arriving between the PING send and read; a parser desync after a previous truncated command; running commands on a connection concurrently from two coroutines (corrupts the request/response pairing).
Related errors
- Connection closed by server.
- Buffer is closed.
- protocol must be an integer
- protocol must be either 2 or 3
- Invalid RESP version
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/c98fc5775361023e.json.
Report an issue: GitHub.