MySQL 'Too Many Connections' (Error 1040): Find and Fix
Your app throws MySQL error 1040 and new requests fail. Find which clients hold the connections, fix the leak, and raise max_connections safely.
MySQL error 1040 "Too many connections" means every connection slot allowed by max_connections is in use, so the server refuses new clients. Log in as an admin, run SHOW PROCESSLIST to see who holds the slots, fix the code that leaves connections open or idle, and only then raise max_connections. Raising the limit alone usually hides a leak for a few days.
The error looks like ERROR 1040 (HY000): Too many connections. In an app it shows up as a 500 on pages that were fine a minute ago. It tends to hit during a traffic spike, right after a deploy, or when a cron job starts many workers at once. This guide shows how to find the cause in order, with commands you can run on a live server.
Step 1: Confirm the Limit and Current Usage
First, check what the server allows and what it has used. Sources such as Percona's guide to this error and the Bytebase reference for error 1040 start the same way.
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
max_connections is the cap. The default in MySQL 8.0 is 151. Threads_connected is how many are open now. Max_used_connections is the highest number since the server started. If it equals the cap, you hit the limit at least once.
MySQL keeps one extra slot for an account with the CONNECTION_ADMIN or SUPER privilege. That is why you can usually still log in as root while the app is locked out. Do not give your app user that privilege, or you lose this escape hatch.
Step 2: See Who Holds the Connections
Run this as soon as you can get in.
SELECT user, host, db, command, COUNT(*) AS n, MAX(time) AS longest_sec
FROM information_schema.processlist
GROUP BY user, host, db, command
ORDER BY n DESC;
Read the result in three ways:
- Many rows with
command = Sleepand a largelongest_sec: the app opened connections and never closed them. This is a leak or a pool that is too large. - Many rows with
command = Queryand a long time: queries are slow, so connections stay busy. This is a slow query problem, not a limit problem. - One
hostwith most of the rows: a single service, worker or cron job is the cause.
Our guide on Postgres queries that are slow in production uses the same method for finding the slow statement. The tooling differs, the logic is the same.
Step 3: Fix the Leak in the App
Idle Sleep connections are the most common cause. Typical sources:
- A script or serverless function opens a new connection on every request and never closes it.
- A pool is sized per process. Ten app processes with a pool of 20 each is 200 connections.
- A background worker or queue consumer holds a connection open while it waits on an external API.
Do the pool maths first. Add up pool size x number of processes x number of app servers, and add jobs and cron. If the total is close to max_connections, shrink the pools.
For a Node.js app with mysql2, create one pool at startup and reuse it.
import mysql from "mysql2/promise";
export const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 10,
queueLimit: 100,
});
// pool.query() releases the connection for you.
// If you call pool.getConnection(), always release it:
const conn = await pool.getConnection();
try {
await conn.query("SELECT 1");
} finally {
conn.release();
}
The finally block matters. Without it, one thrown error leaks one connection forever. This is the same failure we describe in database connection pool exhaustion in Node.js.
PHP and Laravel apps usually do not leak this way, because PHP-FPM closes connections at the end of each request. Look instead at the number of PHP-FPM workers and long-running queue workers.
Step 4: Clear Stuck Connections Right Now
If the site is down and you need relief before you find the cause, kill the idle sessions.
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 300 AND user = 'app_user';
Review the output, then run the statements. Replace app_user with your app's database user so you do not kill your own admin session. This is a stopgap. The leak will refill the slots.
Step 5: Timeouts That Clean Up Idle Connections
MySQL closes a non-interactive idle connection after wait_timeout seconds. The default is 28800, which is 8 hours. That is far too long for a web app with a leak.
SET GLOBAL wait_timeout = 300;
SET GLOBAL interactive_timeout = 300;
Pick a value longer than your pool's idle timeout, or the pool will try to reuse a connection the server already closed and you will see "MySQL server has gone away" errors. Most pool libraries have a setting to recycle connections before that point. Put the same values in my.cnf so they survive a restart.
Step 6: Raise max_connections Safely
If your pool maths is correct and traffic really needs more connections, raise the cap.
SET GLOBAL max_connections = 300;
To keep it after a restart, add it under [mysqld] in my.cnf.
[mysqld]
max_connections = 300
Each connection uses memory for its buffers and threads. Going from 151 to 2000 on a small server can push it into swap or an out-of-memory kill, which is a bigger outage than the one you were fixing. Percona and others advise choosing a value that fits your RAM and workload and checking OS file descriptor limits too. If your host runs managed MySQL (RDS, Cloud SQL, PlanetScale), the limit often depends on the instance size, so the fix may be a bigger instance or a connection proxy.
When many short-lived clients connect, such as serverless functions, put a pooler such as ProxySQL or your provider's proxy in front of MySQL. It lets thousands of clients share a small number of real connections.
Step 7: Get Alerted Before It Hits 100 Percent
Error 1040 is a slow build-up that looks sudden. Watch Threads_connected against max_connections and alert at about 80 percent. Also alert on a rising error rate from your app, since the first sign is often a handful of failed requests, not a database alarm. Our guide to finding bugs before users report them covers the alert side.
How Relia Helps
Error 1040 reaches users as a wall of 500s, and the database is only one of several places to look. Relia watches the users, APIs, database and servers of a live product and joins related signals, such as a deploy, a failing request and users hitting an error, into one incident explained in plain words, with who was affected. It also runs uptime checks on key pages. Setup for Node.js and browser apps is npx relia-sdk@latest init, and there is a Python package and a Composer package for PHP and Laravel. Go, Rust, Java, .NET and Ruby work through standard OpenTelemetry. There is a Free plan and a live demo at app.tryrelia.com/demo.
FAQ
What is the default max_connections in MySQL?
151 in MySQL 8.0. Managed services often set their own value based on instance size.
Can I log in when MySQL says too many connections?
Usually yes, with an account that has CONNECTION_ADMIN or SUPER, because MySQL reserves one extra slot for it. Keep that privilege off your app user.
Is raising max_connections safe?
It is safe in small steps if you have spare memory. Each connection costs RAM, so a large jump can cause swapping or an out-of-memory kill. Fix idle and leaked connections first.
What is the difference between error 1040 and "MySQL server has gone away"?
1040 means the server refused a new connection because the cap was reached. "Gone away" means an existing connection was closed, often by wait_timeout or a restart.