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:XXXThis 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
| Symptom | Root Cause | Immediate Diagnostic Command |
|---|---|---|
| White screen + PDOException 1040 during spikes | MySQL max_connections exhausted | mysqladmin status |
| Connections climb and never drop | Long-running queries / lock waits | SHOW FULL PROCESSLIST; |
| Errors even on low traffic | PHP workers > DB connection budget | SHOW VARIABLES LIKE 'max_connections'; |
| Sleep connections pile up | Persistent connections not tuned | SHOW 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_slotsMySQL 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 SleepLook 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.cnfor database values, take a full snapshot of your Drupal database. A quickmysqldump --single-transaction dbname > backup.sqlcosts 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.cnfUnder the [mysqld] section:
[mysqld]
max_connections = 200
max_user_connections = 180
wait_timeout = 120
interactive_timeout = 120
thread_cache_size = 50The 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=2000For 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 = 500Keep 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 -yAdd 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:rebuildWith 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.