OpenCart Performance Optimization
Standard OpenCart 3.x/4.x without tuning gives TTFB 800–1500ms on medium catalogs. Main reasons: non-optimal database queries, missing OPcache, heavy extensions without caching, uncompressed resources.
Profiling: Where's the Bottleneck
Before optimization—measure. Enable MySQL slow query logging:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
After 30 minutes site operation, analyze:
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
Typical OpenCart slow log problems:
-
SELECT * FROM oc_product_descriptionwithoutlanguage_idindex -
getProducts()does N+1 queries on attributes - shipping and payment modules call external APIs on every cart load
OpenCart Caching
OpenCart 3 has built-in file cache in system/storage/cache/. Switch to Memcached or Redis via system/library/cache.php:
// config.php (in root)
define('CACHE_DRIVER', 'redis');
define('CACHE_PREFIX', 'oc_');
// For redis:
define('REDIS_HOST', '127.0.0.1');
define('REDIS_PORT', 6379);
define('REDIS_AUTH', '');
define('REDIS_DB', 1);
In OpenCart 4 cache configuration via config/opencart.php in cache section:
'cache' => [
'engine' => 'redis',
'expire' => 3600,
'host' => '127.0.0.1',
'port' => 6379,
],
After switching to Redis, catalog page render time drops 40–60% on 5000+ product catalogs.
PHP OPcache and FPM
; opcache.ini
opcache.enable=1
opcache.memory_consumption=192
opcache.max_accelerated_files=8000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1
PHP-FPM setup for OpenCart:
pm = dynamic
pm.max_children = 25
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 1000
pm.max_requests resets worker after 1000 requests—critical for OpenCart where some extensions accumulate state in static variables.
Database Optimization
Indexes missing from standard schema:
-- Speeds up product selection by category with status
ALTER TABLE oc_product ADD INDEX idx_status (status);
ALTER TABLE oc_product_to_category ADD INDEX idx_category (category_id);
-- For popularity/date sorting
ALTER TABLE oc_product ADD INDEX idx_date_added (date_added);
-- Attributes (common N+1)
ALTER TABLE oc_product_attribute
ADD INDEX idx_product_lang (product_id, language_id);
Regular cleanup of expired sessions:
DELETE FROM oc_session WHERE expire < UNIX_TIMESTAMP(NOW() - INTERVAL 7 DAY);
Add to cron once daily.
Nginx Configuration for Static Files and gzip
# Long cache for static files
location ~* \.(jpg|jpeg|png|webp|gif|ico|svg|woff2|css|js)$ {
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml image/svg+xml;
gzip_min_length 1024;
# Brotli (if module installed)
brotli on;
brotli_comp_level 5;
brotli_types text/plain text/css application/json application/javascript image/svg+xml;
Heavy Extensions
Extensions like "super-mega-SEO-pack" often add 5–15 DB queries per page. Diagnostics:
// Temporarily add to index.php before require
$start = microtime(true);
register_shutdown_function(function() use ($start) {
error_log('Page time: ' . round((microtime(true) - $start) * 1000) . 'ms');
});
Disable extensions one by one and compare time—quick way to find culprit.
Images
OpenCart doesn't auto-convert images to WebP. Quick solution via nginx:
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
location /image/ {
try_files $uri$webp_suffix $uri =404;
}
On server convert existing images:
find /var/www/opencart/image -name "*.jpg" -o -name "*.png" | \
xargs -P4 -I{} sh -c 'cwebp -q 80 "$1" -o "${1%.*}.webp"' -- {}
Timeline
OPcache, PHP-FPM, Redis cache, nginx, database indexes setup: 2–3 days. Extension audit and removing problematic ones: 1–2 days. Image conversion to WebP and nginx setup: 0.5 day.







