WordPress wpdb Persistent Connection Limits and PHP‑FPM Child Process Mismatch
0 reputation · 29 Jun 2023, 22:54 UTC
0 reputation · 29 Jun 2023, 22:54 UTC
WordPress relies on the wpdb class to open a MySQL connection for each request. By default it does not maintain an application‑level pool, but persistent connections can be enabled via mysqli.allow_persistent. When PHP‑FPM spawns more child processes than the database permits, the server can emit "Too many connections" errors during traffic spikes.
The core uncertainty lies in whether the current pm.max_children setting is too high for the configured max_connections on the MySQL server, and whether disabling persistent connections would alleviate the intermittent exhaustion without introducing unacceptable latency.
pm.max_children setting is directly causing the "Too many connections" errors?mysqli.allow_persistent in the WordPress environment?wpdb connections before script termination?34775 reputation · 30 Jun 2023, 09:33 UTC
Yes — if mysqli.allow_persistent is enabled, PHP-FPM child processes can hold MySQL links open across requests. When the number of active children approaches or exceeds the effective connection budget on MySQL, traffic spikes produce "Too many connections." This is a likely explanation, not yet a confirmed fact, until you correlate FPM worker count with Threads_connected.
Sample MySQL and PHP-FPM at the same moment during a spike.
mysqladmin -u <user> -p extended-status | grep -E 'Threads_connected|Max_used_connections'mysql -u <user> -p -e "SHOW VARIABLES LIKE 'max_connections'; SHOW VARIABLES LIKE 'max_user_connections';"ps -eo pid,args | grep 'php-fpm: pool' | grep -v grep | wc -lIf Threads_connected climbs toward max_connections while the worker count is also high, and error timestamps align with child spawn events, the correlation is strong. Remember that max_user_connections can cap the WordPress DB user independently of the global limit.
Setting mysqli.allow_persistent=0 forces a fresh TCP connection and MySQL auth handshake for every request. For local Unix-socket or low-latency network deployments, the added overhead is typically small and often in the low-millisecond range, but you should measure it in your own environment. If WordPress connects over SSL or to a remote database host, the handshake cost rises and may become noticeable. In most standard WordPress deployments the trade-off is acceptable because it prevents connection accumulation and stabilizes Threads_connected.
WordPress does not expose a documented hook to close idle wpdb connections before script termination. The wpdb object closes its link in __destruct(), but persistent mysqli links are managed by PHP-FPM and are not returned to a pool that WordPress can drain. You can register a shutdown action, but calling mysqli::close() on a persistent link does not release the underlying MySQL thread in a way that prevents reuse by the same child process. The reliable control point is the PHP-FPM pool configuration and the mysqli.allow_persistent setting, not a WordPress hook.
Apply the setting in the FPM pool, not in wp-config.php:
php_value[mysqli.allow_persistent] = 0
Reload FPM and monitor Threads_connected under load.
One diagnostic detail that changes the recommendation: Is your pool using pm = dynamic, static, or ondemand? Under dynamic, children are recycled and persistent links can accumulate quickly; under ondemand, the worker count stays low and the same exhaustion may not occur even with persistence enabled.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 29 Jun 2023, 23:39 UTC
Persistent mysqli links are tied to the PHP-FPM child, not the request. With mysqli.allow_persistent on, a child keeps the same MySQL thread across requests until the process is recycled or the pool is reloaded.
That means pm.max_requests is the practical release valve. The default is 0, i.e. unlimited, so a child can hold its persistent connection indefinitely even if the request ends. Setting a finite pm.max_requests bounds how long a connection can accumulate in a worker, and the FPM status page pm.status_path shows max children reached and active processes to correlate with Threads_connected.
$wpdb->close() on shutdown releases the link for the current request, but with persistence the underlying socket is typically reused by the next request in the same child unless the process is recycled.