Why Mautic Strains a Shared Database

Mautic is unusually write-heavy for a PHP application. Every page hit on a tracked site, every email open, every link click, and every form submission generates rows in tables like page_hits, email_stats, lead_event_log, campaign_lead_event_log, and lead_points_change_log. On a busy account these tables grow by tens of thousands of rows per day, and because the tracking pixel fires on anonymous visitors too, the growth continues even when campaigns are idle. The database does not shrink on its own, so within a few months a modest Mautic instance can carry a multi-gigabyte schema where 80% of the volume sits in a handful of logging tables.

On a managed shared plan running LiteSpeed on CloudLinux, you share MySQL/MariaDB resources with other tenants, and your account is governed by CloudLinux LVE limits (CPU, physical memory, and EP/entry processes) plus a cap on simultaneous MySQL connections. When Mautic queues cron jobs — mautic:segments:update, mautic:campaigns:trigger, mautic:emails:send — against bloated, poorly indexed tables, each query holds a connection longer, consumes more CPU, and occasionally trips the LVE ceiling. The symptom you see is not always "slow": it frequently appears as SQLSTATE[HY000] [1040] Too many connections, MySQL server has gone away, or an AdminCP that times out mid-save. These are resource and schema problems masquerading as connection problems.

A second failure mode is physical table corruption. MyISAM tables (older Mautic installs sometimes still carry a few) and even InnoDB tables can be marked as crashed after an abrupt MySQL restart, a disk-quota event, or a killed query during a cron run. When that happens Mautic logs entries such as Table './user_mautic/leads' is marked as crashed and should be repaired inside var/logs/mautic_prod.php. Understanding which of these three categories you are dealing with — bloat, missing indexes, or physical corruption — is what determines the correct repair path, all of which you can perform without root access.

Reading the Logs and Confirming the Fault

Start inside the Mautic application log rather than guessing from the browser. Open cPanel File Manager (or DirectAdmin File Manager), navigate to your Mautic document root, and open var/logs/mautic_prod.php. This is the canonical Mautic error log and it records the exact PDO exception, the failing table, and often the SQL state code. Look for recurring patterns: a 1040 code points to connection exhaustion, 1205 Lock wait timeout exceeded points to long-running writes colliding, 2006 MySQL server has gone away usually means a single query exceeded the server's packet or timeout limit, and 145 or 126 indicate a crashed table index.

Because Mautic rotates this file by date, sort File Manager by Last Modified to grab the newest one. If the log is itself enormous (it can reach hundreds of megabytes during an error storm), download it rather than opening it in the browser editor, which may hit the PHP memory limit and show a blank page. If you also see HTTP 500s rather than clean exceptions, check the per-directory error_log that LiteSpeed drops in the same folder as the running script — that captures PHP-level fatals that occur before Mautic's own logger initializes.

Once you know the suspect tables, confirm the scale in phpMyAdmin. Open it from cPanel, select your Mautic database, and run a sizing query so you are working from facts, not assumptions:

SELECT table_name,
       ROUND(data_length/1024/1024,1) AS data_mb,
       ROUND(index_length/1024/1024,1) AS index_mb,
       table_rows
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY (data_length + index_length) DESC
LIMIT 15;

This ranks the heaviest tables immediately. In nearly every troubled Mautic install the top rows are page_hits, email_stats, lead_event_log, and campaign_lead_event_log. Note which ones carry large index_mb values and which carry almost none — a huge data_mb with a tiny index_mb is a strong signal that the table is missing indexes that Mautic's own queries assume exist.

Repairing Broken Tables and Connection Errors

For a table flagged as crashed, phpMyAdmin exposes the repair operation without needing shell access. In phpMyAdmin, select your database, tick the checkbox next to the affected table in the table list, then choose Repair table from the With selected dropdown at the bottom. For MyISAM tables this runs REPAIR TABLE and rebuilds the index file. For InnoDB tables the REPAIR TABLE statement is a no-op, so instead run an OPTIMIZE TABLE, which InnoDB internally converts to a table rebuild (ALTER TABLE ... FORCE) that resolves most index inconsistencies:

OPTIMIZE TABLE page_hits;
CHECK TABLE lead_event_log;

Run CHECK TABLE first to see the reported status; if it returns anything other than OK, follow with the rebuild. Do these one table at a time rather than selecting all tables at once — a single OPTIMIZE on a multi-gigabyte page_hits table can run for minutes, and batching several will hold connections open long enough to trip your account's query timeout or LVE CPU limit, which then produces the very gone away error you are trying to eliminate.

Connection-exhaustion errors (1040 Too many connections) on shared hosting are rarely fixable by raising a limit, since max_connections lives in the server-wide my.cnf you cannot touch. The workable approach is to stop Mautic from opening so many concurrent connections. Open app/config/local.php in File Manager and confirm cron commands are not overlapping — if the segment update, campaign trigger, and email send jobs all run on the same minute, they compete for connections and CPU. Stagger them in cPanel's Cron Jobs interface so each runs in its own window (for example segments at */15, campaigns at an offset, email sends on a separate slot), and add the --max-rows or --batch-limit flags so each run processes a bounded set rather than attempting the entire backlog in one connection-heavy pass.

If you administer background jobs across several apps on the same account, the pattern of staggering overlapping workers to avoid resource spikes is the same one covered in our Invoice Ninja scheduler guide, and the connection-budgeting logic transfers directly.

Fixing Missing Indexes and Trimming Data

Slow Mautic queries almost always trace back to filtering or joining on an unindexed column. To find them, pull a candidate query from mautic_prod.php (it logs the SQL on timeouts) and run it through EXPLAIN in the phpMyAdmin SQL tab:

EXPLAIN SELECT * FROM lead_event_log
WHERE lead_id = 12345 AND date_added > '2026-01-01';

If the type column reads ALL and key is NULL, the query is doing a full table scan. Add a composite index that matches the filter order. Mautic ships sensible indexes by default, but version upgrades and manual data imports sometimes leave them missing on large logging tables:

ALTER TABLE lead_event_log
  ADD INDEX idx_lead_date (lead_id, date_added);

Create indexes during low-traffic hours because the ALTER locks the table while it builds, and keep the column order aligned with how Mautic queries — equality columns first, range columns last. Re-run EXPLAIN afterward to confirm key now names your new index.

Indexing a bloated table only goes so far; the durable fix is removing the data Mautic no longer needs. Mautic has a built-in maintenance command, mautic:maintenance:cleanup, that purges old visitor hits, anonymous leads, and event log rows older than a chosen age. Add it as a cPanel cron job rather than running ad-hoc, for example a weekly run that keeps 365 days of history:

php /home/user/public_html/bin/console mautic:maintenance:cleanup --days-old=365 --no-interaction

Run it once manually with --dry-run appended first so you can see the row counts it intends to delete before committing. After the cleanup completes, the freed space inside InnoDB is not returned to disk automatically — run OPTIMIZE TABLE on the trimmed tables in phpMyAdmin to reclaim it and rebuild the indexes against the smaller row set. Combined, scheduled cleanup plus periodic optimization keeps page_hits and the event logs from drifting back into the state that triggered the connection errors and timeouts, so the maintenance becomes preventive rather than reactive.