Bali, Indonesia
Insights

Raising max_connections Won't Fix Too Many Connections Errors on Its Own

October 21, 2026
0:00
When a site starts throwing 'Too many connections' (MySQL error 1040), the quickest fix most people find is raising max_connections. Sometimes the error goes away for a day. Then it comes back, or the server starts swapping, or MySQL gets killed by the kernel. That pattern usually means the limit was never the real problem. The misconceptions below come up often around this error. For each one: why it feels reasonable, and what is usually going on underneath.
This one feels obvious because the error message says 'too many'. If there are too many, allow more. The catch is what decides how many connections are open at once. It is roughly how many new connections arrive per second, multiplied by how long each one stays open. As an illustrative example, 20 requests per second that each hold a connection for a tenth of a second is about 2 connections at any moment. Keep the traffic the same, but make each query take 5 seconds, and you need about 100. Visitors did not double. Queries got slower. That is why the error so often shows up on a normal traffic day. Common reasons a query suddenly holds its connection much longer:
  • A missing index, so a query scans a large table.
  • Lock waits. A long write or an ALTER TABLE holds a lock, and everything behind it queues up. In the process list this often shows as 'Waiting for table metadata lock'.
  • Slow storage, where every query waits on disk.
Raising the limit in this situation lets more requests join the same queue. They all wait on the same bottleneck. Each extra running query also competes for the same CPU and disk, which can make every query a little slower. The pileup gets bigger instead of smaller. From the support side, the first question is whether the connections are busy or idle. That one distinction points to completely different fixes.
This feels true because an idle connection really is cheap. MySQL does not reserve memory for every slot you allow. Setting the limit to a large number does not change memory use the moment you restart. The cost shows up under load. Each connection runs in its own thread with its own stack. When a query needs to sort, join, or read in certain ways, MySQL allocates per-session buffers for it, controlled by settings like sort_buffer_size, join_buffer_size, read_buffer_size, and read_rnd_buffer_size. In-memory temporary tables can grow up to the smaller of tmp_table_size and max_heap_table_size. Global memory like innodb_buffer_pool_size is shared. These per-connection pieces are not. The usual worst-case estimate is global buffers plus max_connections times the per-session buffers. That formula overestimates, since not every connection allocates every buffer at once. The direction is still right. The moment connections pile up is exactly when queries are heavy, so it is also when per-connection memory peaks. On a small VPS this plays out in a predictable way. Memory runs out, the system starts swapping, and eventually the kernel's OOM killer picks a process to stop. mysqld is often the largest process, so it gets chosen. A connection error turns into a full database outage, followed by InnoDB crash recovery on restart. One more detail: the value you set is not always the value MySQL uses. If the open file limit is too low, MySQL may lower the effective max_connections at startup and log a warning. After any change, check the running value instead of trusting the config file.
The process list shows connections, so it is natural to read each row as active work. In practice, many rows often show Sleep in the Command column. Those connections are open and idle, and each still takes a slot. Idle connections pile up for a few common reasons:
  • Code that opens a connection and never closes it, especially on error paths, or in long-running workers that open a fresh connection per job.
  • PHP persistent connections, enabled with the p: host prefix in mysqli or PDO::ATTR_PERSISTENT. Each PHP worker keeps its own connection open between requests. The connection count then follows the number of PHP workers, not your traffic.
  • Connection pools that are sized larger than needed, or that leak connections which are never returned. If the pool sits behind a proxy, socket handling adds another layer. That is covered in this piece on socket leaks and pools behind Envoy.
The worker count matters even without persistent connections. A PHP-FPM worker usually holds at most one database connection at a time. So the most connections PHP can open is roughly the sum of pm.max_children across all pools that talk to that database. If that sum is higher than max_connections, the error is a sizing mismatch. The cleaner fix is often to lower worker counts to what the server can serve, not to raise the database limit. wait_timeout controls how long an idle connection lives before the server closes it. In many MySQL builds the default is 8 hours, so a slow leak has plenty of time to fill every slot. Lowering it cleans up leftovers. Treat it as a backstop, though, not the fix. An app that keeps a connection around and expects it to still be alive will start logging 'MySQL server has gone away' instead.
On shared hosting, the limit you hit is often a per-account one. That is max_user_connections, and it produces a different error: 'User ... has exceeded the 'max_user_connections' resource' (error 1203). The server as a whole may have plenty of room left. This gets confused a lot because applications rarely show the raw MySQL error. WordPress, for example, shows its generic 'Error establishing a database connection' page, and that is what ends up in a support chat. Before changing anything, find the actual MySQL error in the application log or the PHP error log. On a shared server the account owner usually cannot change either limit. Fixing the workload is the only lever available anyway. It also helps to know that MySQL reserves one extra connection beyond max_connections for an administrative account, one with SUPER or, in MySQL 8.0, CONNECTION_ADMIN. When the app is locked out, that slot is often how you get in to look around. Do not let your application connect with that account, or the spare slot is gone when you need it.
These read-only checks answer most of the question. Run them from a MySQL client. You need the PROCESS privilege to see other users' connections in the process list.
SQL
SHOW GLOBAL VARIABLES LIKE 'max_connections';
SHOW GLOBAL VARIABLES LIKE 'wait_timeout';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Threads_running';
SHOW FULL PROCESSLIST;
Max_used_connections is the peak since the server last started. If it equals or sits right at max_connections, you have hit the ceiling at some point. The more useful readings come from catching the problem while it is happening:
  1. If Threads_running is high and the process list shows many queries with large values in the Time column, queries are stuck. Read the State column for lock waits. Turn on the slow query log (slow_query_log with a sensible long_query_time) to find what is slow. The fix is an index, a rewritten query, or moving heavy jobs away from peak hours.
  2. If Threads_connected is high but Threads_running is low, and most rows show Sleep, the problem is connection handling. Look at persistent connections, worker counts, pool sizes, and code that does not close connections. Then consider a shorter wait_timeout as cleanup.
  3. If connections are short-lived, queries are fast, and you legitimately need more concurrency, for example because you deliberately added app servers or workers, raising max_connections is the right move. Budget the memory first, and confirm the new value is actually in effect after the restart.
The third case is real, but it is the least common of the three. Raising the limit before you know which case you are in usually just postpones the same error to a busier moment, or swaps it for an out-of-memory crash. A practical rule of thumb: treat 'Too many connections' as a question about how long connections stay open, not how many are allowed. Check whether they are busy or sleeping, fix the cause on that side, and only then decide whether the server needs a bigger limit and has the memory to back it.