redis/redis ยท error

\nI/O error\n

Error message

\nI/O error\n

What it means

Printed by cliAuth() in src/redis-cli.c:1620 when redisCommand(ctx, "AUTH ...") returns a NULL reply, meaning the hiredis context is already in an error state before the server could respond to AUTH. It is not an authentication rejection (that path produces error 71) but a transport-level failure: the underlying socket broke, timed out, or the connection's read/write failed while the AUTH command was in flight.

Source

Thrown at src/redis-cli.c:1620

    tcsetattr(STDIN_FILENO, TCSANOW, &mode);
}

/*------------------------------------------------------------------------------
 * Networking / parsing
 *--------------------------------------------------------------------------- */

/* Send AUTH command to the server */
static int cliAuth(redisContext *ctx, char *user, char *auth) {
    redisReply *reply;
    if (auth == NULL) return REDIS_OK;

    if (user == NULL)
        reply = redisCommand(ctx,"AUTH %s",auth);
    else
        reply = redisCommand(ctx,"AUTH %s %s",user,auth);

    if (reply == NULL) {
        fprintf(stderr, "\nI/O error\n");
        return REDIS_ERR;
    }

    int result = REDIS_OK;
    if (reply->type == REDIS_REPLY_ERROR) {
        result = REDIS_ERR;
        fprintf(stderr, "AUTH failed: %s\n", reply->str);
    }
    freeReplyObject(reply);
    return result;
}

/* Send SELECT input_dbnum to the server */
static int cliSelect(void) {
    redisReply *reply;
    if (config.conn_info.input_dbnum == config.dbnum) return REDIS_OK;

    reply = redisCommand(context,"SELECT %d",config.conn_info.input_dbnum);

View on GitHub (pinned to 3acc0c49cf)

Solutions

  1. Verify the server is still running and reachable right before the client connects: redis-cli ping or redis-cli -h <host> -p <port> ping.
  2. Check that no proxy or firewall between client and server is terminating the connection mid-handshake; increase idle/proxy timeouts.
  3. Reproduce with a longer connect timeout (--connect-timeout) to rule out REDIS_ERR_TIMEOUT masquerading as I/O error.
  4. If using TLS, confirm the TLS negotiation succeeded first (error 78 would normally surface, but a half-broken context can leak here).
  5. File a reproducible packet capture if the socket genuinely closes between accept and AUTH.

Example fix

// before: implicit short timeout may drop mid-AUTH
redis-cli -h host -p 6379 -a secret
// after: explicit, generous connect timeout + retry
redis-cli -h host -p 6379 -a secret --connect-timeout 30
Defensive patterns

Strategy: retry

Validate before calling

import socket
import redis

def assert_reachable(host, port, timeout=2.0):
    # Cheap liveness probe before issuing AUTH so a dropped socket
    # is caught as a connect error, not an I/O error mid-handshake.
    try:
        with socket.create_connection((host, port), timeout=timeout):
            return True
    except OSError:
        return False

# Call before constructing the client:
if not assert_reachable(redis_host, redis_port):
    raise ConnectionError(f"Redis {redis_host}:{redis_port} unreachable")
client = redis.Redis(host=redis_host, port=redis_port, socket_keepalive=True,
                     socket_connect_timeout=2.0, socket_timeout=2.0,
                     health_check_interval=30)

Try / catch

# An I/O error on AUTH means the server closed/reset the socket during the
# handshake (reply == NULL). Treat as transient: reconnect + retry with
# bounded exponential backoff.
from redis.exceptions import ConnectionError as RedisConnectionError
import time

def auth_with_retry(factory, password, attempts=5, base=0.1):
    last = None
    for i in range(attempts):
        client = factory()
        try:
            client.ping()
            return client
        except RedisConnectionError as e:
            last = e
            time.sleep(base * (2 ** i))
    raise last

Prevention

When it happens

Trigger: cliAuth() is invoked during cliConnect() (line 1771) right after a TCP/unix socket is established. The NULL reply occurs when config.conn_info.auth is non-NULL and the hiredis context has context->err set (REDIS_ERR_IO, REDIS_ERR_EOF, or REDIS_ERR_TIMEOUT) at the moment redisCommand() is called.

Common situations: Server killed or restarted between accept() and AUTH; a proxy/load-balancer (e.g. Envoy, HAProxy) closing idle connections before AUTH completes; TLS handshake that left the context in an error state but was not caught; network partition on a slow link where the connection succeeds TCP-wise but drops before the first command round-trip.

Related errors


AI-assisted analysis of redis/redis@3acc0c49cf (2026-08-01). Data as JSON: /data/errors/a9056da91a844bd4.json. Report an issue: GitHub.