The Redis error "ERR max number of clients reached" means the server has as many open connections as maxclients allows, so it is turning new ones away. Commands on existing connections keep working, and memory and CPU may look completely normal. It is a capacity limit, but in most incidents the limit is not what needs fixing. Something is holding far more connections than it should, or the operating system capped Redis lower than you think.
What exactly happens at the limit
The check is in networking.c, and it runs when a connection is accepted, before a client object is even created. If the number of clients plus cluster bus connections is at or above maxclients, Redis writes the error, increments a counter and closes the socket. The client sees:
ERR max number of clients reached
A few details from that code matter in practice:
- In cluster mode the text is different: "ERR max number of clients + cluster connections reached". The cluster bus counts against the same limit. The redis.conf comments note that every node uses two bus connections, one incoming and one outgoing, and that very large clusters need the limit sized accordingly.
- TLS clients may never see the message. The rejection happens before the TLS handshake, and the code comment says nothing is written in that case, so the connection just drops. Your application then logs a connection reset or handshake failure instead of the Redis error.
- Replicas count too. The limit check uses the full client list, which includes replica connections.
connected_clientsinINFOsubtracts replicas, so on a primary with replicas the numbers won't add up exactly. - Your admin session has no reserved slot. If every slot is taken,
redis-cligets refused like everyone else. That's why the first fix often has to come from the client side.
Valkey has the same check and the same message. Valkey 9.2, a release candidate as of this writing, adds maxclients-reserved and priority-subnets to its configuration. They keep a share of the limit for connections from listed admin or monitoring networks, so operators are not locked out. Redis itself has no equivalent setting.
First check: what is maxclients really?
The default is 10000. The Redis client handling docs explain the catch: Redis needs maxclients plus 32 file descriptors (32 are reserved for internal use). At startup it checks its open files limit and, if that is too low, tries to raise it itself. The code in server.c steps down until the OS accepts a value. If it can't get enough, Redis lowers maxclients to fit. It doesn't refuse to start. It logs a warning and carries on with the smaller limit.
So if the process's hard limit is 1024 open files, 10000 quietly becomes 992 without anyone changing the config. Check what the running process actually has:
# the real limit of the running process
grep 'open files' /proc/$(pidof redis-server)/limits
# what Redis decided at startup
grep -i 'maxclients\|open files' /var/log/redis/redis-server.log
The warnings to look for say "maxclients has been reduced to N to compensate for low ulimit", or "Unable to obtain the current NOFILE limit" when Redis couldn't read it and assumed 1024. If Redis runs under systemd, the ulimit in your shell does not apply to the service. The limit comes from the unit's LimitNOFILE= setting.
You can change maxclients at runtime with CONFIG SET. The config code re-runs the file limit adjustment first, and if the OS can't support the new value, the command fails with "The operating system is not able to handle the specified number of clients, try with N". So CONFIG SET won't silently go past the OS limit.
Second check: who is holding the connections
Start with the numbers. If you can't get in because all slots are taken, run these from a host whose connection is already open, use a monitoring agent's session, or free a few slots first (see below).
redis-cli INFO clients
# connected_clients:9968
# cluster_connections:0
# maxclients:10000
# blocked_clients:12
redis-cli INFO stats | grep -E 'rejected_connections|total_connections_received'
A rejected_connections counter that keeps climbing confirms the limit is being hit right now. Then group the connections. CLIENT LIST prints one line per connection with fields like addr, name, age, idle, flags, cmd and, from Redis 7.2, lib-name and lib-ver:
# connections per client IP
redis-cli CLIENT LIST | grep -o 'addr=[^:]*' | sort | uniq -c | sort -rn | head
# connections per client name / library (if clients set them)
redis-cli CLIENT LIST | grep -o 'name=[^ ]*' | sort | uniq -c | sort -rn | head
redis-cli CLIENT LIST | grep -o 'lib-name=[^ ]*' | sort | uniq -c | sort -rn | head
The patterns we tend to find:
- One host or service with thousands of connections. A connection leak: a client created per request and never closed, a pool with no upper bound, or a framework that opens a new pool in every worker process.
- Huge
idlevalues on many connections. Connections abandoned by clients that crashed or were killed without closing them. Redis's owntimeoutdefaults to 0, so such connections stay open until TCP keepalive notices the peer is gone. Keepalive is on by default at 300 seconds, and the config comment says it takes about twice that to close a dead connection on Linux. - Everything scaled at once. An autoscaler doubled the pods and each pod opens a fixed pool. Pool size × instances is the number to compare with
maxclients. - Many
blpop/xreador subscribers. Blocking commands and Pub/Sub keep a connection busy for as long as they wait. Each such consumer needs its own connection, so they add up quickly.
Fixes, from immediate to permanent
# free slots now: close one connection by its address
redis-cli CLIENT KILL ADDR 10.20.0.15:53122
redis-cli CLIENT KILL TYPE normal MAXAGE 86400 # Redis 7.4+, by connection age
# close idle connections automatically (seconds, 0 = never)
redis-cli CONFIG SET timeout 300
# raise the ceiling if the OS limit allows it
redis-cli CONFIG SET maxclients 20000
Free slots now. CLIENT KILL can close connections by ID, ADDR, LADDR, USER or TYPE, and since Redis 7.4 by MAXAGE. Note that MAXAGE closes clients older than the given number of seconds: it is connection age, not idle time. There is no idle filter, so to close only idle connections, take the IDs from CLIENT LIST and kill those. Restarting the leaking service often frees more slots, faster, than anything you do on the server.
Set an idle timeout. timeout closes normal connections idle for longer than the given number of seconds. According to timeout.c, it does not apply to replicas, the master link, blocked clients or Pub/Sub subscribers, so it is fairly safe to enable. The docs warn the check runs incrementally, so connections close a bit later than the exact value. Set it longer than your pools' own idle timeout, so the pool closes the connection first and doesn't hit a dead socket.
Raise the limit if the load is real. If the connection count is legitimate, raise the OS file limit first (LimitNOFILE= or ulimit -n), then maxclients in the config file and with CONFIG SET. Each connection also uses memory for its buffers. Check tot-mem in CLIENT LIST and our Redis OOM and maxmemory guide before you multiply the client count.
maxclients only buys time, and the next incident comes with more memory in use and more file descriptors open. Fix the client: one shared pool per process, a hard upper bound on pool size, and connections returned after use.Prevent the next one
- Alert on the ratio
connected_clients / maxclients, for example at 80%. Also alert on any increase inrejected_connections. Both are inINFO, and they fit with the other database metrics that predict incidents. - Make clients identify themselves.
CLIENT SETNAMEwith the service name turns a wall of IP addresses into an answer in seconds. Many libraries also sendlib-name. - Budget connections on paper: pool size × processes × instances at maximum scale, plus replicas, cluster bus and monitoring, should stay well under
maxclients. - Check the effective limit after every deploy or OS change.
CONFIG GET maxclientson the running server, not the number in the config file.
Key takeaways
- "ERR max number of clients reached" means open connections hit
maxclients. Existing connections keep working. - The default is 10000, but Redis lowers it at startup to fit the file descriptor limit minus 32. Check the log and
CONFIG GET maxclients. - Cluster bus connections and replicas count against the limit. TLS clients may only see a dropped connection.
- Find the culprit with
CLIENT LISTgrouped byaddr,nameandlib-name, and look atidle. - Use
timeoutandCLIENT KILLfor relief, fix the client pool for good, and raise limits only for real load.