Your Drupal site was humming along fine, then a traffic spike hit and the front page went white. Check the logs and you get the classic gut-punch:

PDOException: SQLSTATE[HY000] [1040]: Too many connections in
 /home/user/public_html/core/lib/Drupal/Core/Database/Connection.php:XXX

This isn't a Drupal bug. It's your database hitting its max_connections ceiling while PHP keeps opening new sockets faster than MySQL/MariaDB can serve or recycle them. Let's fix the real cause instead of just bumping numbers blindly.

Quick Diagnostic Cheat-Sheet

SymptomRoot CauseImmediate Diagnostic Command
White screen + PDOException 1040 during spikesMySQL max_connections exhaustedmysqladmin status
Connections climb and never dropLong-running queries / lock waitsSHOW FULL PROCESSLIST;
Errors even on low trafficPHP workers > DB connection budgetSHOW VARIABLES LIKE 'max_connections';
Sleep connections pile upPersistent connections not tunedSHOW STATUS LIKE 'Threads_connected';

Understand the Connection Budget

Every PHP process that renders a Drupal page opens at least one database connection. If you run 60 PHP-FPM/LiteSpeed workers and MySQL only allows 50 connections, a busy moment guarantees the 1040 error. The math has to balance:

Total PHP workers  ≤  max_connections - reserved_admin_slots

MySQL reserves one extra connection for SUPER users, which is why you can often still log in via CLI when the site is down.

Step 1 — Measure the Current Ceiling

Log in and check what you're actually working with:

mysql -e "SHOW VARIABLES LIKE 'max_connections';"
mysql -e "SHOW STATUS LIKE 'Threads_connected';"
mysql -e "SHOW STATUS LIKE 'Max_used_connections';"

If Max_used_connections equals max_connections, you've been hitting the wall. Note that value.

Step 2 — Inspect What's Holding Connections Open

Catch the offenders in the act during a spike:

mysql -e "SHOW FULL PROCESSLIST;" | grep -v Sleep

Look for queries stuck in Sending data, Copying to tmp table, or Waiting for table lock. A single slow query from a poorly indexed views block can hold dozens of connections hostage during peak load.

Warning: Before editing any my.cnf or database values, take a full snapshot of your Drupal database. A quick mysqldump --single-transaction dbname > backup.sql costs seconds and saves careers.

Step 3 — Raise max_connections Sensibly

Don't set it to 5000 and walk away — each connection consumes RAM (buffers, thread stack). On a VPS, calculate headroom first:

# Rough per-connection memory
# read_buffer_size + sort_buffer_size + join_buffer_size + thread_stack

# Edit MariaDB config
nano /etc/my.cnf.d/server.cnf

Under the [mysqld] section:

[mysqld]
max_connections = 200
max_user_connections = 180
wait_timeout = 120
interactive_timeout = 120
thread_cache_size = 50

The wait_timeout drop from the default 28800 seconds to 120 is critical — it forces MySQL to reap idle sleeping connections instead of letting them rot for eight hours.

systemctl restart mariadb
mysql -e "SHOW VARIABLES LIKE 'max_connections';"

Step 4 — Cap PHP Workers to Match the Database

This is the step most people skip. If you raise DB connections but leave PHP workers unbounded, you just move the crash point higher. On LiteSpeed, control this via the External App / LSAPI settings; the key value is Max Connections and the environment variable:

PHP_LSAPI_CHILDREN=50
LSAPI_MAX_REQS=2000

For PHP-FPM setups, edit the pool:

nano /etc/php-fpm.d/www.conf

pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500

Keep pm.max_children comfortably below max_user_connections. On shared plans, CloudLinux LVE already caps concurrent processes per account — that isolation actually protects your neighbors from this exact runaway.

Step 5 — Kill the Real Load with Caching

The best connection is one you never open. Drupal's biggest connection saver is serving cached pages so PHP never touches MySQL at all. Wire up Redis for the cache and lock bins:

composer require drupal/redis
drush pm:enable redis -y

Add to settings.php:

$settings['redis.connection']['interface'] = 'PhpRedis';
$settings['redis.connection']['host'] = '127.0.0.1';
$settings['redis.connection']['port'] = 6379;
$settings['cache']['default'] = 'cache.backend.redis';
$settings['cache']['bins']['bootstrap'] = 'cache.backend.redis';
$settings['cache']['bins']['discovery'] = 'cache.backend.redis';
$settings['cache']['bins']['config'] = 'cache.backend.redis';

Then confirm the page cache and dynamic page cache modules are on and BigPipe is enabled for authenticated users:

drush pm:enable page_cache dynamic_page_cache big_pipe -y
drush cache:rebuild

With anonymous traffic served from cache and sessions/locks moved to Redis, your MySQL connection count during a spike drops dramatically because most requests never open a DB handle.

Step 6 — Verify Under Simulated Load

Don't wait for the next real spike to find out. Hammer it and watch connections:

ab -n 2000 -c 50 https://yoursite.com/

# In another terminal
watch -n 1 "mysql -e 'SHOW STATUS LIKE \"Threads_connected\";'"

If Threads_connected plateaus well under your max_connections, the fix holds. If it still climbs to the ceiling, you have slow queries — go back to Step 2 and add indexes.

Warning: Never run load tests against a live production node during business hours. Clone the site to a staging VPS or run tests in a low-traffic window.

When to Move Off Shared Hosting

If your site legitimately needs 150+ concurrent PHP workers and a large InnoDB buffer pool, you've outgrown a shared connection budget. A dedicated Cloud VPS gives you full control over my.cnf and PHP worker counts without noisy neighbors. For catalog-heavy Drupal Commerce sites doing thousands of writes, a Dedicated Server with a tuned InnoDB buffer pool ends the connection fights entirely.

Frequently Asked Questions

Does increasing max_connections slow down MySQL?

Not directly, but each allowed connection reserves memory for buffers and thread stacks. Setting it far higher than your RAM supports can trigger swapping or OOM kills, which is worse than the original error. Size it to real worker counts plus headroom.

Should I use persistent database connections in Drupal?

Generally no. Persistent connections keep sockets open between requests and can pile up idle connections that exhaust the pool faster than they help. Rely on a tuned thread_cache_size and a healthy wait_timeout instead.

Why do connections stay in Sleep state?

A long wait_timeout (default 8 hours) keeps finished connections alive doing nothing. Lower it to 60–120 seconds so MySQL reclaims idle threads and frees slots for new requests during a spike.

Connection exhaustion almost always traces back to two things: too many uncached page renders and mismatched worker-to-connection budgets. On Hostiso, LiteSpeed's efficient LSAPI process management, built-in Redis object caching, and NVMe-backed MariaDB with sane defaults mean your Drupal site absorbs traffic spikes without ever hitting the 1040 wall. Cache the anonymous traffic, keep your worker count aligned with your database budget, and let the infrastructure do the heavy lifting.