Why max_connections matters
Every WordPress request that touches the database opens a MySQL connection. If MySQL's max_connections limit is exceeded, new requests get rejected:
mysqli_connect(): (HY000/1040): Too many connectionsWordPress logs this as "Error establishing database connection."
Set the limit too low: traffic spikes overwhelm you. Set too high: MySQL runs out of memory and crashes.
How many connections do you actually need
MySQL allocates memory per connection. Each holds 1-30MB.
Formula:
max_connections × per_connection_memory + base_memory ≤ available RAMFor typical WordPress: - per_connection_memory ≈ 4-8 MB - base_memory ≈ 60-80% of total RAM (mostly innodb_buffer_pool)
Practical rule:
max_connections = (Total RAM × 0.20) / per_connection_memoryFor 8GB RAM with 6 MB/connection:
max_connections = (8192 × 0.20) / 6 ≈ 270So 250-300 max_connections is reasonable for an 8GB server.
Match to PHP-FPM workers
grep pm.max_children /etc/php/8.1/fpm/pool.d/www.confIf pm.max_children = 50, you have at most 50 simultaneous PHP requests.
max_connections = pm.max_children + 20For pm.max_children=50, set max_connections=70-100.
Detecting current usage
SHOW STATUS LIKE 'Max_used_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';If Max_used_connections consistently 80%+ of max_connections, need more capacity or fewer connections.
Setting max_connections
In /etc/mysql/my.cnf:
[mysqld]
max_connections = 200Restart MySQL (or SET GLOBAL max_connections = 200; for live).
Tuning per-connection memory
If you can't reduce concurrent connections:
[mysqld]
sort_buffer_size = 256K
read_buffer_size = 128K
read_rnd_buffer_size = 256K
join_buffer_size = 256KWordPress queries don't need large buffers. Reducing to 256K saves memory.
Configuring wait_timeout
Idle connections still consume a slot. wait_timeout controls how long.
Default: 28800 (8 hours). Way too long.
[mysqld]
wait_timeout = 60
interactive_timeout = 600wait_timeout=60 frees up slots quickly. Often the highest-impact tuning change.
Persistent connections caveat
WordPress doesn't use persistent connections by default. Don't enable them.
Check wp-config.php:
// REMOVE if present:
define('DB_PERSISTENT', true);And php.ini:
mysqli.allow_persistent = OffHandling sudden spikes
ProxySQL — sits between PHP and MySQL. PHP connects to ProxySQL (cheap). ProxySQL pools real connections. Allows 5x concurrency.
Rate limiting — Cloudflare or Nginx rejects excess requests at the edge.
http {
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location / {
limit_req zone=mylimit burst=20 nodelay;
}
}
}Monitoring max_connections
# Cron: alert if connections > 80%
0 * * * * /var/www/yoursite/scripts/check-mysql-conn.sh#!/bin/bash
MAX=$(mysql -BNe "SHOW VARIABLES LIKE 'max_connections';" | awk '{print $2}')
CURRENT=$(mysql -BNe "SHOW STATUS LIKE 'Threads_connected';" | awk '{print $2}')
THRESHOLD=$((MAX * 80 / 100))
if [ "$CURRENT" -gt "$THRESHOLD" ]; then
echo "MySQL connections at $CURRENT / $MAX" | mail -s "Alert" admin@yoursite.com
fiCommon mistakes
- Setting max_connections to 5000 — wastes memory, doesn't help
- Not adjusting wait_timeout — connections pile up
- Tuning without measuring — don't know what you need
- Restarting MySQL during business hours — use SET GLOBAL for live changes
When to call a specialist
MySQL tuning is part of every performance audit. Often we find a server with max_connections=300 and pm.max_children=10 — wasting RAM on slots that will never be used.
MySQL tuning as part of speed recovery. For broader work see speed recovery.

