profile before you tune
Measure where time is actually spent before touching config. Enable the built-in profiler by setting the environment to development temporarily in .env and check the LiteSpeed access log for slow responses. From the shell, get a real page timing:
curl -o /dev/null -s -w 'connect:%{time_connect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://yourstore.tld/A high TTFB with low transfer time points at PHP or MySQL, not network. Confirm which PHP handler and version the domain uses in DirectAdmin's PHP Selector or cPanel's MultiPHP Manager. osCommerce 4 requires PHP 8.1+; run it on PHP 8.3 for the best OPcache and JIT behaviour.
configure opcache for the code base
osCommerce 4 is a Symfony-based application with a large class map, so OPcache sizing matters more than on the legacy 2.x tree. Check what is loaded now:
php -i | grep -Ei 'opcache.(enable|memory|max_accelerated|validate|jit)'Set these in the per-domain PHP configuration through the panel (DirectAdmin Select PHP Version > Options or cPanel MultiPHP INI Editor) so CloudLinux LVE keeps them scoped to the account:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=30000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.jit=tracing
opcache.jit_buffer_size=64MThe framework autoloads thousands of files; max_accelerated_files=30000 prevents cache thrashing. On a production store that changes rarely, set opcache.validate_timestamps=0 and reset OPcache manually after each deploy:
php -r 'opcache_reset();'If you leave revalidation on, keep revalidate_freq at 60 so PHP is not stat-ing every file on each request.
move sessions and cache to redis
File-based sessions and cache on a busy catalog generate constant small I/O inside the LVE. Point osCommerce 4 at Redis instead. Confirm the extension and server first:
php -m | grep redis
redis-cli pingIf redis-cli is not reachable, request Redis on the account or a dedicated socket from your host, then bind PHP sessions to it. Set in the per-domain PHP INI:
session.save_handler=redis
session.save_path="tcp://127.0.0.1:6379?database=1"For the application cache, osCommerce 4 uses Symfony's cache component. Configure a Redis adapter in config/packages/cache.yaml so doctrine metadata, routing, and system cache live in memory:
framework:
cache:
app: cache.adapter.redis
system: cache.adapter.redis
default_redis_provider: 'redis://127.0.0.1:6379/2'Use separate Redis databases (1 for sessions, 2 for app cache) so you can flush one without wiping the other. After changing cache config, rebuild:
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prodThe same session/object-cache split solves the I/O bottleneck the way it does in other PHP applications; see the approach in this MySQL and Redis optimization writeup for the general pattern.
enable litespeed full-page caching
OPcache and Redis speed up dynamic generation, but LiteSpeed can skip PHP entirely for anonymous visitors. osCommerce 4 does not ship a dedicated LSCache plugin, so use LiteSpeed's rule-based caching for guest catalog pages while excluding cart, checkout, and admin.
Add cache rules to the document root .htaccess (LiteSpeed reads these natively):
<IfModule LiteSpeed>
CacheLookup on
RewriteEngine On
# Do not cache authenticated or transactional paths
RewriteRule ^(admin|checkout|cart|account|login) - [E=Cache-Control:no-cache]
# Do not cache when a cart/session cookie is present
RewriteCond %{HTTP_COOKIE} (PHPSESSID|osc_cart|customer_logged_in) [NC]
RewriteRule .* - [E=Cache-Control:no-cache]
# Public cache for everything else, 5 minutes
RewriteRule .* - [E=Cache-Control:max-age=300]
</IfModule>Adjust the cookie names to match what your store actually sets — inspect them with your browser dev tools on an added-to-cart request rather than assuming. Verify a hit with:
curl -sI https://yourstore.tld/ | grep -i x-litespeed-cacheA response showing x-litespeed-cache: hit confirms the page served from cache. If every request is a miss, a cookie or query string is defeating the rules; narrow the exclusion conditions.
find and fix slow catalog queries
The product listing and search pages are where osCommerce query cost concentrates. Turn on the slow query log to see the real offenders:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;
SET GLOBAL slow_query_log_file = '/var/log/mysql/oscommerce-slow.log';Let it run under normal traffic, then summarise with mysqldumpslow -s t /var/log/mysql/oscommerce-slow.log. Category and filter pages commonly do full scans on the products-to-categories join. Confirm with EXPLAIN and add covering indexes where the plan shows ALL:
EXPLAIN SELECT p.products_id FROM products p
JOIN products_to_categories p2c ON p.products_id = p2c.products_id
WHERE p2c.categories_id = 21 AND p.products_status = 1;
ALTER TABLE products_to_categories ADD INDEX idx_cat_prod (categories_id, products_id);
ALTER TABLE products ADD INDEX idx_status (products_status);Check the InnoDB buffer pool is large enough to hold the working set. If most reads hit disk, raise it:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';On a shared or reseller box you cannot change global MySQL variables — index tuning and query reduction are your levers there. Where you control the server, set innodb_buffer_pool_size to roughly 60–70% of available RAM and restart MySQL during a low-traffic window.
reduce front-end payload
Build production assets so the browser is not fetching dozens of source files. From the store root:
php bin/console cache:clear --env=prod
php bin/console assets:install --env=prodEnable Brotli or gzip and browser caching for static files in .htaccess, which LiteSpeed applies without a reload:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/webp "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>Convert product images to WebP and serve them at the dimensions actually displayed; oversized thumbnails scaled in the browser are a frequent cause of slow catalog rendering that no server cache will fix.
verify the result
Re-run the same curl timing you started with and compare TTFB before and after each change so you know which tier gave the gain. A tuned osCommerce 4 store on PHP 8.3 with OPcache JIT, Redis sessions, and LiteSpeed caching serving guest pages should return a warm homepage well under 200ms TTFB. If PHP time stays high despite a cache hit, confirm the request is genuinely bypassing PHP rather than regenerating on every load.