Autopsy of a global timeout: debugging i/o, MySQL, and Apache bottlenecks on cPanel
Back to blog

Autopsy of a global timeout: debugging i/o, MySQL, and Apache bottlenecks on cPanel

9/4/2026 · 6 min · Servers

When dozens of websites on a shared hosting node experience concurrent timeouts while webmail grinds to a halt, the initial reaction is often to suspect an external network attack or unexpected traffic surges. However, in environments combining cPanel and CloudLinux, user-facing symptoms are often merely the tail end of a domino effect originating much deeper in the storage stack.

Recently, I troubleshot an incident where baseline metrics seemed contradictory: CPU utilization remained low, tenant resource limits showed no violations, yet database processes were piling up in an unresponsive backlog.

Here is the operational breakdown that uncovered the actual root cause: tracing performance from CloudLinux LVE governance down to asymmetric block latency in NVMe RAID arrays, followed by bypassing disk I/O bottlenecks using spare server RAM.


1. Initial alerts and ruling out individual CloudLinux limits#

The incident surfaced with multiple tenants reporting intermittent gateway timeouts on WordPress sites alongside sluggish Roundcube logins.

On CloudLinux servers, the first troubleshooting step is verifying whether individual tenant accounts are exceeding their resource ceilings (Entry Processes - EP, CPU SPEED, or Memory). Running lveinfo or top-lve provides an immediate snapshot:

        ID     EP    PNO    TNO     SPEED      MEM         IO       IOPS
  tenant1       0      1      1        0%        8          0          0
  tenant2       0      1      1        0%        7          0          0
  tenant3       0      2      2        0%       18          0          0
  store4        2      5      5        0%       14          0          0
  blog5         1      3      3        0%       11          3          0
  portal6       3      7      7        2%       17          2          0

Every user account reported negligible CPU consumption, with entry processes well below standard thresholds (typically set between 20 and 40).

No single tenant was hitting resource limits. The degradation was systemic and global.


2. The avalanche of sleep states in MySQL#

When slowdowns impact an entire server simultaneously, the database engine is the primary suspect. Checking active database threads via CLI:

mysqladmin proc status

The terminal returned hundreds of accumulated connections:

+---------+-----------------------+-----------+-----------------------+---------+------+----------+------------------+
| Id      | User                  | Host      | db                    | Command | Time | State    | Info             |
+---------+-----------------------+-----------+-----------------------+---------+------+----------+------------------+
| 2319092 | app_user1             | localhost | app_db1               | Sleep   | 16   |          |                  |
| 2319246 | app_user2             | localhost | app_db2               | Sleep   | 29   |          |                  |
| 2319268 | app_user3             | localhost | app_db3               | Sleep   | 27   |          |                  |
| 2319394 | portal_blog           | localhost | portal_blog           | Sleep   | 18   |          |                  |
| 2319413 | store_db              | localhost | store_db              | Sleep   | 17   |          |                  |
| 2319681 | root                  | localhost |                       | Query   | 0    | starting | show processlist |
+---------+-----------------------+-----------+-----------------------+---------+------+----------+------------------+
Uptime: 51430  Threads: 144  Questions: 20479295  Slow queries: 266  Opens: 182740  Open tables: 2000  Queries per second avg: 398.197

Key takeaways from this output:#

  1. Persistent Sleep states: The Sleep status indicates that MySQL already completed processing the query. However, the client PHP process had not closed the connection because it was blocked waiting on other downstream resources (such as filesystem I/O or web server network buffers).
  2. Table cache saturation: The Open tables: 2000 metric sat pegged against the ceiling configured in table_open_cache.
  3. High query throughput: The engine was handling approximately 400 queries per second (QPS).

MySQL was visibly burdened by connection queues, but it was merely the first visible casualty of an upstream choke point.


3. Auditing CPU and incoming network connections#

Checking overall system activity with top showed the server operating with over 90% idle CPU capacity, with Apache workers and security monitoring daemons leading process lists:

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
2993897 nobody    20   0 2123948 387044  18404 S  28.6   0.3   1:05.26 /usr/sbin/httpd -k start
   2086 root      20   0 4204892 127552  17380 S  14.3   0.1  26:44.25 /usr/sbin/imunify-auditd-log-reader
2994423 nobody    20   0 2123952 376868  18368 S  14.3   0.3   1:09.60 /usr/sbin/httpd -k start

To determine whether malicious external sources were flooding port 80:

netstat -anp | grep :80 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n | tail -10

Connections were normally dispersed across unique public IP addresses, with no single client maintaining more than 10 simultaneous connections. Layer 7 volumetric attacks were ruled out.


4. Uncovering the real bottleneck: asymmetric latency in NVMe RAID 1#

With processor capacity available, network traffic calm, and MySQL accumulating idle connections, storage I/O was the remaining critical layer.

Executing iostat with detailed sampling:

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           1.39    0.00    0.78    0.07    0.00   97.76

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz  f_await  aqu-sz  %util
md127            0.00      0.00     0.00   0.00    0.00     0.00   41.00    776.50     0.00   0.00    9.68    18.94     0.00    0.40  14.00
nvme0n1          0.00      0.00     0.00   0.00    0.00     0.00   73.00    735.00     0.00   0.00    0.29    10.07     0.31    0.03   1.20
nvme1n1          0.00      0.00     0.00   0.00    0.00     0.00   76.00    807.00     0.00   0.00    9.16    10.62     8.70    0.98  25.50

The disk disparity revealed:#

The host ran a software RAID 1 mirror (md127) comprised of two NVMe drives (nvme0n1 and nvme1n1).

Examine the w_await column (average write latency per block):

In synchronous RAID 1 mirroring, block writes are only acknowledged after both member drives confirm completion. The entire host was forced to crawl at the speed of its slowest disk.

Investigating the lagging drive#

Querying nvme1n1 with smartctl:

smartctl -a /dev/nvme1n1

The output showed:

Model Number:                       KINGSTON SNV3S4000G
Total NVM Capacity:                 4,000,787,030,016 [4.00 TB]
SMART overall-health self-assessment test result: PASSED
Percentage Used:                    2%

SMART self-tests passed cleanly, with only 2% endurance used. The issue was not hardware failure, but hardware suitability:

The Kingston NV3 is a consumer-grade, DRAMless drive utilizing QLC NAND flash, relying heavily on a temporary dynamic SLC cache.

When exposed to an enterprise cPanel workload (characterized by continuous InnoDB transaction logging and thousands of small session files written each minute (heavy random write I/O)) the drive's SLC cache quickly exhausts. Sustained write speeds drop below traditional spinning HDDs, causing write latency to spike dramatically.


5. Immediate mitigation: using server RAM as an i/o shield#

Physically swapping the drive for an enterprise-grade TLC NVMe with dedicated DRAM requires a scheduled datacenter maintenance window. To stabilize the production workload immediately, spare RAM was deployed to absorb disk pressure.

Checking memory allocation:

free -h
               total        used        free      shared  buff/cache   available
Mem:           125Gi        26Gi        18Gi       407Mi        81Gi        98Gi
Swap:           15Gi          0B        15Gi

The host possessed 125 GB of RAM, with 98 GB completely available.

Yet, checking the active /etc/my.cnf:

[mysqld]
innodb_buffer_pool_size = 4294967296
max_connections = 500
wait_timeout = 60

MySQL had been constrained to a conservative 4 GB (4294967296 bytes) buffer pool. This forced the database to constantly read from and write to the sluggish NVMe array despite nearly 100 GB of unused memory sitting idle.

Tuning /etc/my.cnf:#

I scaled the buffer pool by 8x and expanded table cache descriptors:

[client-server]
!includedir /etc/my.cnf.d

[mysqld]
# --- MEMORY BUFFER TUNING (Bypassing Disk) ---
# Expanding Buffer Pool to keep active databases in memory
innodb_buffer_pool_size = 32G
table_open_cache = 10000
table_definition_cache = 10000

# --- MITIGATING I/O LATENCY ---
# Reduces synchronous fsync operations for routine transactions
innodb_flush_log_at_trx_commit = 2

# --- CONCURRENCY CEILINGS ---
max_allowed_packet = 268435456
open_files_limit = 69000
max_connections = 1000
wait_timeout = 60
interactive_timeout = 60
long_query_time = 5

innodb_file_per_table = 1
sql_mode = ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

Restarting MariaDB (systemctl restart mariadb) enabled the engine to serve active queries directly from system memory, instantly relieving write pressure from the NVMe mirror.


6. Apache recovery: resolving splitlogs pipe stalls#

While SQL queries sped up significantly, Apache error logs still recorded backlog warnings:

[log_config:warn] [pid 3922139:tid 3922562] (32)Broken pipe: [client 198.51.100.39:37482] AH00646: Error writing to |/usr/local/cpanel/bin/splitlogs --dir=/etc/apache2/logs/domlogs...
[lsapi:warn] [pid 4030908:tid 4031167] Backend log: PHP Warning: session_start(): open(/opt/alt/php56/var/lib/php/session/sess_..., O_RDWR) failed: Disk quota exceeded (122)

Two distinct problems caused this:

  1. Stalled splitlogs daemon: cPanel's utility for splitting Apache domain logs (domlogs) could not process the sudden flush of queued requests released by MySQL. When the pipe buffer fills, Apache halts client connections and logs Broken pipe.
  2. Quota exhaustion on a legacy account: The Disk quota exceeded warning did not refer to root filesystem storage (which had 1.2 TB available), but to an individual cPanel account that had exhausted its allocated quota, blocking PHP session writes.

To clear stalled pipes and rebuild Apache configurations cleanly:

# Terminate lingering splitlogs processes
killall -9 splitlogs 2>/dev/null

# Rebuild Apache virtual host configurations
/scripts/rebuildhttpdconf

# Force-restart the web daemon
/scripts/restartsrv_httpd --hard

Apache restarted cleanly with empty queues. The account reporting disk quota exhaustion had its storage allocation increased in WHM, eliminating the session write failure.


7. Validating response latency with cURL#

With backend services restored, testing end-to-end response times through verbose curl (-Iv) verified that timeouts had cleared:

curl -Iv https://webmail.example.com

The output confirmed responsive handshake and delivery times:

* Host webmail.example.com:443 was resolved.
* Connected to webmail.example.com (198.51.100.246) port 443
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
< HTTP/1.1 200 OK
< Content-Length: 37940
< Set-Cookie: webmailsession=...; HttpOnly; path=/; port=443; secure

The TLSv1.3 negotiation completed in milliseconds, returning an HTTP 200 response with zero latency.


Operational takeaways for preventing server cascading timeouts#

This incident highlights critical operational takeaways for high-density environments:

  1. Consumer hardware struggles under multi-tenant random I/O: DRAMless QLC SSDs (such as the Kingston NV3) quickly saturate their SLC cache under persistent transactional workloads. In a RAID 1 mirror, a single lagging drive drags down the performance of the entire array.
  2. Allocate available RAM to the database buffer pool: If a host has 64 GB or 128 GB of free memory, keeping innodb_buffer_pool_size at 2 GB or 4 GB wastes hardware and forces unnecessary disk I/O.
  3. Monitor w_await in iostat: Monitoring raw %util or MB/s metrics alone can obscure severe per-block write latency bottlenecks.
  4. Log pipes can bottleneck web servers: Utility log dispatchers like splitlogs can freeze Apache workers if incoming traffic bursts exceed pipe buffer drain rates.

Tuning storage I/O, database caching, and web server pipes cohesively ensures resilient infrastructure that stays fast during sustained traffic.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments