Why phpList Feels Slow on Shared Hosting

phpList is a mailing-list manager, not a typical page-serving CMS, so its performance profile is unusual. Most public visitors only touch a handful of lightweight pages (subscribe, confirm, unsubscribe, and the preferences form), while the heavy work happens inside the admin area and the message-processing queue. When customers report that phpList is slow, the complaint almost always falls into one of three buckets: the admin dashboard takes many seconds to render, the subscribe/confirm pages lag under bot traffic, or the send process (processqueue) crawls and appears to hang.

The underlying causes are predictable. phpList bootstraps a large amount of PHP on every request, so without a bytecode cache the server recompiles the same files repeatedly. Statistics and subscriber-count queries scan large tables in the phplist_user_user and phplist_listuser schema, which is expensive when the account shares a MySQL instance with other users. The send process is deliberately throttled by configuration values, and if those are misread as a bug, people assume the software is broken rather than paused. On top of that, phpList runs on generous default memory and execution needs that shared PHP handlers do not always grant.

Before changing anything, confirm what is actually slow. Open cPanel > Metrics > Errors or the per-domain error_log in the phpList directory using File Manager. Look for repeated Allowed memory size ... exhausted or Maximum execution time ... exceeded entries, which point to PHP limits rather than the application. A second quick test: append ?page=home&pi=CommonPlugin style navigation and time each admin section. If only the Statistics pages are slow, the bottleneck is database work; if every page is slow including the login screen, the bottleneck is PHP compilation or memory. This distinction decides which fixes below apply.

PHP Selector, OPcache, and Sane Resource Limits

The single most effective change for a sluggish phpList admin area is enabling a bytecode cache so PHP stops recompiling on every hit. On CloudLinux accounts, go to cPanel > Software > Select PHP Version (or the PHP Selector in DirectAdmin under Extra Features). Choose PHP 8.1 or 8.2, which phpList 3.6+ supports well, then open the Extensions tab and confirm opcache is ticked. Also verify that mbstring, gd or imagick, curl, and pdo_mysql are enabled, because phpList silently degrades or errors when these are missing.

Next open the Options tab in the same PHP Selector screen. These map to per-account PHP directives you are allowed to change without root. Reasonable values for phpList are:

memory_limit = 256M
max_execution_time = 120
max_input_vars = 3000
upload_max_filesize = 32M
post_max_size = 32M
opcache.enable = On

The elevated max_execution_time matters for admin operations like importing subscribers or reconciling bounces, which iterate over thousands of rows in a single request. If the Options screen does not expose OPcache tuning, set it through a .user.ini file placed in the phpList document root (for example /home/USER/public_html/lists/.user.ini). LiteSpeed and the CloudLinux PHP handler read this file per directory:

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
memory_limit=256M
max_execution_time=120

Keep opcache.validate_timestamps=1 so that when you upgrade phpList or edit config.php, the cache picks up changes within the revalidate window instead of serving stale bytecode. Changes to .user.ini take effect after the PHP process pool recycles, typically within a minute or two, or immediately if you toggle the PHP version in Selector to force a restart. After enabling OPcache, reload the admin dashboard twice: the second load should be noticeably faster because the compiled opcodes are now cached in memory.

LiteSpeed, .htaccess Caching, and Protecting Public Pages

phpList admin pages are personalized and must never be full-page cached, but the public assets and confirmation pages benefit from browser and edge caching. Because Hostiso runs LiteSpeed, you can control caching behavior through .htaccess in the phpList root. Add long-lived cache headers for static files while explicitly excluding the dynamic entry points:

<IfModule LiteSpeed>
  CacheLookup on
</IfModule>

# Never cache the dynamic phpList endpoints
<FilesMatch "(index|admin|dl|ut|lt)\.php$">
  Header set Cache-Control "no-cache, no-store, must-revalidate"
</FilesMatch>

# Cache static assets aggressively
<FilesMatch "\.(css|js|png|jpe?g|gif|svg|woff2?|ico)$">
  Header set Cache-Control "public, max-age=2592000"
</FilesMatch>

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json
</IfModule>

Do not enable LiteSpeed full-page caching for phpList output. Unlike WordPress with LSCache, phpList has no cache-purge plugin, so a full-page cache would serve one subscriber's personalized preferences page to another visitor. The safe scope is static assets and compression only.

Public subscribe pages are frequently hammered by spam bots, and that traffic inflates PHP-worker usage and makes legitimate admin requests wait. Rate-limit obvious abuse at the .htaccess level rather than inside phpList. For example, block requests missing a referer on the subscribe endpoint or throttle a known-bad user agent:

RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (semrush|ahrefs|mj12bot) [NC]
RewriteRule .* - [F,L]

Combine this with phpList's built-in reCAPTCHA (enabled under Config > Settings > Security) so bot submissions never reach the database. Reducing junk writes to phplist_user_user keeps the subscriber tables lean, which directly speeds up the count and segment queries the dashboard runs.

Send-Process Throttling and Database Housekeeping

The most misread performance issue is a "slow" send. phpList intentionally paces delivery to respect provider limits. Those knobs live in config/config.php, editable through File Manager > Edit. If sends stall or the browser times out while processing the queue, review these values:

define('MAILQUEUE_BATCH_SIZE', 300);
define('MAILQUEUE_BATCH_PERIOD', 3600);
define('MAILQUEUE_THROTTLE', 1);
define('MAX_PROCESS_MESSAGE', 999);
define('WORKAROUND_OUTLApache', 0);

On shared hosting the outbound mail rate is capped by the server, so setting a batch size larger than your hourly mail allowance only causes failures. Match MAILQUEUE_BATCH_SIZE to your account's hourly send limit and leave MAILQUEUE_THROTTLE at a small positive value to spread delivery. For reliability, run the queue from cPanel > Cron Jobs instead of the browser so a page timeout never interrupts a campaign:

*/5 * * * * /usr/local/bin/php /home/USER/public_html/lists/admin/index.php -p processqueue -c /home/USER/public_html/lists/config/config.php

Running the queue via cron also keeps the admin UI responsive, since sending no longer occupies the interactive PHP worker. Set define('MANUALLY_PROCESS_QUEUE', 1); if you want the web UI to only trigger, not run, long sends.

Finally, address database bloat, which is the usual cause of slow Statistics and subscriber pages. Open phpMyAdmin and inspect table sizes. The phplist_user_message_forward, phplist_linktrack_forward, and phplist_user_message_bounce tables grow indefinitely on active installs. From the phpList admin, use Config > Manage bounces to purge processed bounces, and periodically run table optimization in phpMyAdmin by selecting the phpList tables and choosing Optimize table from the dropdown. This defragments indexes and reclaims space without any server-level command. Keeping these tables trimmed, combined with OPcache and a cron-driven queue, resolves the overwhelming majority of phpList slowdowns on a shared account.