go-redis/redis · error
redis: client is closed
Error message
redis: client is closed
What it means
pool.ErrClosed is returned by any pool/client operation attempted after Close() (pool.go:57-58). Once the client or pool is closed, Get/NewConn/Put and derived operations (pubsub open, etc.) fail immediately with this sentinel rather than attempting I/O.
Source
Thrown at internal/pool/pool.go:58
CloseReasonMaintNotificationsDisabled = "maintnotifications_disabled"
)
// Metric state constants for connection state tracking.
// These represent the logical state of a connection from a metrics perspective,
// not the internal state machine state (ConnState).
const (
// MetricStateIdle indicates the connection is idle in the pool,
// ready to be acquired.
MetricStateIdle = "idle"
// MetricStateUsed indicates the connection is currently being used
// by a client operation.
MetricStateUsed = "used"
)
var (
// ErrClosed performs any operation on the closed client will return this error.
ErrClosed = errors.New("redis: client is closed")
// ErrPoolExhausted is returned from a pool connection method
// when the maximum number of database connections in the pool has been reached.
ErrPoolExhausted = errors.New("redis: connection pool exhausted")
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
ErrPoolTimeout = errors.New("redis: connection pool timeout")
// ErrConnUnusableTimeout is returned when a connection is not usable and we timed out trying to mark it as unusable.
ErrConnUnusableTimeout = errors.New("redis: timed out trying to mark connection as unusable")
// errHookRequestedRemoval is returned when a hook requests connection removal.
errHookRequestedRemoval = errors.New("hook requested removal")
// errConnNotPooled is returned when trying to return a non-pooled connection to the pool.
errConnNotPooled = errors.New("connection not pooled")
// errConnEvictedIdle is passed to OnRemove hooks when a pooled connection is evicted onView on GitHub (pinned to 36d97525cd)
Solutions
- Do not reuse a client after Close(); create a new client for the new lifecycle.
- Coordinate close with a WaitGroup/context so in-flight ops finish first.
- Guard with client.(*redis.Client) and a closed flag, or check errors.Is(err, redis.ErrClosed).
Example fix
// before client.Close() client.Get(ctx, key) // ErrClosed // after client.Close() client = nil // on next use, lazily construct a new client
Defensive patterns
Strategy: validation
Validate before calling
// Track lifecycle yourself; go-redis has no public Closed() check.
// Don't reuse after Close(); recreate when needed.
if client == nil {
client = redis.NewClient(opts)
} Type guard
// There is no public Closed() accessor on *redis.Client.
// Track closure in your own wrapper.
type safeClient struct{ c *redis.Client; closed bool } Try / catch
err := client.Get(ctx, key).Err()
if errors.Is(err, redis.ErrClosed) {
// recreate the client for the new lifecycle
client = redis.NewClient(client.Options())
} Prevention
- Never reuse a client after Close(); nil the reference.
- Coordinate shutdown so in-flight ops complete before Close().
- Own a single client lifecycle per logical service.
When it happens
Trigger: Calling client.Get/Set/Subscribe (or pool.Get) after client.Close() has been invoked; common in shutdown ordering bugs, reusing a client across goroutines where one closes it, or tests not resetting the client.
Common situations: Graceful-shutdown races, deferred Close() running before an in-flight op, or a long-lived shared client closed by one consumer.
Related errors
- redis: connection not available
- redis: connection not available for write operation
- redis: connection pool exhausted
- redis: connection pool timeout
- redis: timed out trying to mark connection as unusable
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/00dd079cca1e1ed5.json.
Report an issue: GitHub.