PrestaShop Performance Optimization: Speed Up Your Store
Complete performance tuning guide — OPcache, MySQL tuning, CCC configuration, image optimization, caching strategies, and Core Web Vitals.
Speed is a conversion problem, not a vanity metric
We build Performance Revolution, our caching, Critters and PurgeCSS module, and we run it on the high-traffic shops we operate ourselves. The pattern is depressingly consistent: most PrestaShop stores are slow for the same five reasons, and four of them are fixable in an afternoon.
Speed pays out in three places:
- Conversions. On the Hummingbird shops we run, dropping median LCP from 4s to 2s typically lifts mobile conversion by 30–60%.
- SEO. Google has used Core Web Vitals as a ranking signal since 2021. Slow shops earn less organic traffic and pay more for paid traffic to compensate.
- Mobile. 70% of e-commerce traffic is mobile. A page that feels fine on a MacBook can be a 6-second blank on a mid-range Android.
Speed is a maintenance discipline, not a project you finish. Every module, every theme tweak, every image nudges it up or down. We keep an LCP baseline for every client shop and check it after every deploy. See our performance tuning notes for the database, cache and measurement work behind those gains.
Measure before you touch anything
The fastest way to waste a day is to start "optimising" without numbers. Every shop we audit gets the same three-page measurement first (homepage, category, product) because each one exercises a different part of the stack.
The tools we actually use
- Google PageSpeed Insights: Free, uses real Chrome user data (CrUX). Run it against all three page types. Category and product pages are where most stores fall apart.
- GTmetrix: The waterfall tells you which request is blocking render, which module ships 600KB of unused CSS, and which third-party script adds 800ms before TTFB.
- WebPageTest: Filmstrip view from different continents and connection profiles. Use when GTmetrix and PSI disagree.
Core Web Vitals — what each one tells you
- LCP (Largest Contentful Paint): When the largest visible element finishes painting. Target under 2.5s. On most PrestaShop shops it's the hero image or category banner, and the killer is render-blocking CSS plus an unoptimised image without preload.
- INP (Interaction to Next Paint): How fast the page reacts to a tap or click. Replaced FID in March 2024. Target under 200ms. Usual suspects: jQuery-heavy modules, tag managers, and click handlers doing too much synchronously.
- CLS (Cumulative Layout Shift): How much the page jumps during load. Target under 0.1. Caused by images without width/height, late banners, and font swaps without a matched fallback.
Realistic targets
A real PrestaShop shop with cart logic, search, modules and tracking will not score 100. Our targets: mobile 60–80, desktop 85–95, LCP under 2.5s mobile / 1.8s desktop, total weight under 2MB, fewer than 80 requests on first paint.
Don't chase a 98 by stripping out what makes you money. A 65 with healthy conversion beats a 98 on a page nobody buys from.
Server: where most of the latency hides
No front-end cleverness will save you from a slow backend. If PrestaShop takes 800ms to render HTML before a single asset request goes out, that's your floor.
PHP OPcache — non-negotiable
OPcache holds compiled PHP bytecode in memory so the interpreter doesn't re-parse the same files on every request. PrestaShop loads hundreds of PHP files per page. Without OPcache, you pay that parse cost every time.
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.save_comments=1
The defaults are wrong for PrestaShop. max_accelerated_files needs to be at least 20,000 — a typical PS install has 10–15k PHP files, and the default of 4,000 means most of your code gets thrown out and re-parsed. memory_consumption should be 128–256MB. If the cache fills, OPcache evicts entries and you silently lose the benefit.
On cPanel hosts with validate_timestamps=0, OPcache will never re-read PHP files from disk on its own. You must reset it via a web request after every deploy — a CLI reset only clears the CLI pool, not the web pool. We've debugged "my fix isn't deploying" tickets that turned out to be exactly this.
MySQL / MariaDB — the one setting that matters
A PrestaShop product page typically runs 80–200 SQL queries. A category page with filters can blow past 400. The single most important variable is innodb_buffer_pool_size:
[mysqld]
# Set to 50-70% of available RAM on dedicated DB server
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
# MySQL 5.7 / MariaDB only (removed in MySQL 8.0)
query_cache_type = 1
query_cache_size = 64M
# Find problem queries
slow_query_log = 1
long_query_time = 1
# Connection settings
max_connections = 150
tmp_table_size = 64M
max_heap_table_size = 64M
If your database is 500MB and your buffer pool is 1GB, most queries serve from RAM. Turn on the slow query log and read it weekly — a query that takes 1.2s idle takes 12s under traffic, and that's how Black Friday goes sideways. Most slow queries we find come from the search controller, layered-nav filters on category pages, and stats modules that should have been disabled years ago.
Hosting tiers — be honest with yourself
- Shared ($5–15/mo): You share CPU and RAM with hundreds of neighbours. Fine for a 200-product test shop, not a real business.
- VPS ($20–60/mo): Where almost every shop we operate sits. 4GB RAM minimum, 2 cores, NVMe SSD. Sweet spot up to a few hundred orders a day.
- Dedicated ($80–300/mo): 1,000+ daily orders or catalogues over 100k products. Also for anyone running their own Varnish / Redis stack.
If you're on shared hosting and slow, moving to a decent VPS buys you more speed than every other tip on this page combined. We've watched LCP drop from 6s to 2.2s on the host swap alone, nothing else changed.
PHP-FPM and Redis
Use PHP-FPM rather than mod_php — separate process pool, better memory behaviour, easier to tune for traffic spikes. Use Redis for session and cache storage instead of the filesystem. Configuration in app/config/parameters.php:
'ps_cache_enable' => true,
'ps_caching' => 'CacheRedis',
PrestaShop's built-in switches
The Performance page in PrestaShop 8 Back Office.
This is where Smarty cache, debug mode, CCC, and the caching engine live. Most performance tuning starts here.
CCC (Combine, Compress, Cache)
Under Advanced Parameters → Performance. CCC concatenates and minifies your CSS and JS. Always enable in production. Things we've been bitten by:
- Files marked
deferorasyncstay separate, by design. - External resources (Google Fonts, Stripe.js, Cloudflare scripts) are never combined.
- Modules that inject raw
<link>tags throughdisplayHeaderinstead ofactionFrontControllerSetMediabypass CCC entirely. Every audit turns up at least one. Our hooks page covers the fix. - If CCC breaks checkout or a widget, the module is doing something order-dependent. Find it, fix it or replace it — don't leave CCC off.
Smarty template settings
Set template caching to "Recompile templates if the files have been updated" on production. Never "Force compilation" — that recompiles every template on every request, the worst possible Smarty mode.
Debug mode — check this first, every time
Debug mode disables most caching, forces recompilation and logs aggressively. On production the only correct value is false:
// In app/config/defines.inc.php — MUST be false on production
define('_PS_MODE_DEV_', false);
We've inherited shops running in debug for months. The "mystery slowness" disappears the moment we flip the flag. Always check on the first audit pass.
Disable modules you don't use
Every enabled module costs you: autoloader entries, hook callbacks, possibly CSS/JS on every page, possibly DB queries even when there's nothing to display. A fresh install ships with a long list of modules — you don't need most of them.
Go through Modules → Module Manager and uninstall (not just disable) anything you don't actively use. Three analytics modules doing the same job? Keep one. If you need a structured cleanup pass before tuning, Cleanup Revolution helps clear operational cruft before you start measuring.
Images — usually 60–80% of your page weight
The biggest, easiest LCP wins live here. We've watched homepage LCP drop from 4.2s to 1.6s on a single shop just by regenerating images at the right sizes and serving WebP.
WebP and sensible dimensions
WebP is 25–35% smaller than JPEG with no visible quality loss. PrestaShop 8+ supports it natively; on older versions use server-side conversion or a module.
The most common waste: 2000×2000px source files served into a 300px thumbnail slot. Configure image types under Design → Image Settings to match what your theme actually renders. Don't upload 4000px masters "just in case" — 1200px is enough for retina product zoom.
Lazy loading
Defer everything below the fold:
<img src="product.jpg" loading="lazy" width="300" height="300" alt="Product Name">
Do not lazy-load the header logo, hero banner or first product row. Those are LCP candidates — lazy-loading them tells Chrome to deprioritise the element it's measuring you on. We've seen this one mistake cost a shop 1.5s of LCP. For catalogues where theme templates are inconsistent, Automatic SEO Images Lazy Tags can apply the below-the-fold pattern at scale.
Regenerating after a change
After changing image type dimensions, regenerate via Back Office or the CLI:
php bin/console prestashop:image:regenerate --type=products
Front-end optimisations that actually move the needle
Cut HTTP requests
Open your waterfall. Over 100 requests is a problem. Usual offenders: every module loading its own tiny CSS/JS file, Google Fonts with four weights and italics, social-share widgets pulling their own SDKs, two tag managers fighting each other.
Critical CSS
Browsers block rendering until every CSS file in <head> has downloaded. Critical CSS inlines the styles needed for the initial viewport into the HTML, then loads the full stylesheet asynchronously. On mobile this routinely shaves 1–2s off LCP for us. Performance Revolution generates critical CSS automatically via Critters and keeps it in sync when theme CSS changes — by hand on every theme tweak is a chore that never gets done.
JavaScript: defer is your friend
Use defer for almost everything — the script downloads in parallel but doesn't execute until HTML is parsed. Use async only for genuinely independent scripts like analytics. In PrestaShop 1.7+:
$this->context->controller->registerJavascript(
'module-my-script',
'modules/mymodule/views/js/front.js',
['position' => 'bottom', 'priority' => 200, 'attribute' => 'defer']
);
Fonts
Custom fonts quietly add 200–400KB. What we do on every shop:
- Self-host rather than pulling from Google Fonts — saves a DNS lookup and a TLS handshake.
- Subset to the characters you need. Latin-only is 60–80% smaller than full Unicode.
font-display: swapso visible text never waits on a font.- Two weights max. 400 and 700 cover almost every theme.
- WOFF2 only — best compression, universal modern support.
@font-face {
font-family: 'YourFont';
src: url('/themes/your-theme/assets/fonts/yourfont.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
CDN basics
A CDN serves static files from servers near your visitors. Cloudflare is the obvious free start. Point images, CSS and JS at the CDN domain via Advanced Parameters → Performance → Media Servers.
Database optimisation
PrestaShop databases bloat silently. Tables that were fine at launch turn into millstones after two years of cart abandonment, bot traffic and module logs nobody reads.
Clean up old carts
ps_cart and ps_cart_product get a row per visitor — including every bot that crawled the catalogue. After a year these tables routinely hit millions of rows, and every join against them slows down.
-- Delete cart products for old abandoned carts (not linked to orders)
DELETE cp FROM ps_cart_product cp
INNER JOIN ps_cart c ON cp.id_cart = c.id_cart
LEFT JOIN ps_orders o ON c.id_cart = o.id_cart
WHERE o.id_order IS NULL
AND c.date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);
-- Delete the empty carts
DELETE c FROM ps_cart c
LEFT JOIN ps_orders o ON c.id_cart = o.id_cart
LEFT JOIN ps_cart_product cp ON c.id_cart = cp.id_cart
WHERE o.id_order IS NULL AND cp.id_cart IS NULL
AND c.date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);
Back up first, staging first. The LEFT JOIN + IS NULL pattern is what protects carts that became orders — get it wrong and you orphan order history.
Logs and connections
-- Application logs
DELETE FROM ps_log WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);
-- Visitor tracking
DELETE FROM ps_connections_page WHERE id_connections IN (
SELECT id_connections FROM ps_connections
WHERE date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH)
);
DELETE FROM ps_connections WHERE date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);
-- Orphaned guest records
DELETE g FROM ps_guest g
LEFT JOIN ps_connections c ON g.id_guest = c.id_guest
WHERE c.id_guest IS NULL;
-- Search stats, 404 logs, email logs
DELETE FROM ps_statssearch WHERE date_add < DATE_SUB(NOW(), INTERVAL 6 MONTH);
DELETE FROM ps_pagenotfound WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);
DELETE FROM ps_mail WHERE date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);
Search index
If you use Elasticsearch, Algolia or any external search backend, the built-in search tables are dead weight PrestaShop still rebuilds:
TRUNCATE TABLE ps_search_index;
TRUNCATE TABLE ps_search_word;
Optimise tables and add indexes
After big deletions, reclaim space:
OPTIMIZE TABLE ps_cart, ps_cart_product, ps_connections,
ps_connections_page, ps_guest, ps_log;
Indexes we've added on nearly every shop we audit:
-- Check existing indexes first: SHOW INDEX FROM ps_cart;
ALTER TABLE ps_cart ADD INDEX idx_cart_date (date_add);
ALTER TABLE ps_connections ADD INDEX idx_conn_date (date_add);
ALTER TABLE ps_orders ADD INDEX idx_orders_ref (reference);
ALTER TABLE ps_product ADD INDEX idx_product_ref (reference);
Run EXPLAIN on queries from your slow log. If type says ALL, the database is full-scanning. The right index typically turns a 4-second query into 12ms.
Caching strategies
Full page cache
The biggest single lever you have. Without it, every anonymous request executes the whole stack — hundreds of PHP files, 100+ queries, 200–500ms of render. With it, the same page goes out in 5–20ms. On the Hummingbird shops we run, enabling FPC typically cuts mobile LCP by 1.2–1.8s on top of everything else.
Varnish is the industry default. nginx FastCGI cache is simpler if you already run nginx:
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PS:100m inactive=60m;
set $skip_cache 0;
if ($http_cookie ~* "PrestaShop-") { set $skip_cache 1; }
if ($request_uri ~* "/cart|/order|/my-account|/admin") { set $skip_cache 1; }
location ~ \\.php$ {
fastcgi_cache PS;
fastcgi_cache_valid 200 10m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
}
The hard part isn't enabling it — it's invalidation. When a price changes, stock drops, or an admin edits a category, the right cached pages have to drop. That complexity is why so many shops never turn FPC on. It's also what Performance Revolution handles for you on PrestaShop.
Object caching and browser caching
Object caching (Redis) keeps query results in memory between requests. Smaller gain than FPC (typically 30–50% off query time) but invalidation is automatic, so it's the safe first step.
Browser caching headers tell visitors' browsers to keep static files locally:
# Apache (.htaccess)
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
The things that quietly destroy performance
- Too many enabled modules. Every one costs you — autoloader entries, hook callbacks, often CSS/JS, often queries. A lean shop almost always beats a bloated one on identical hardware. No module is free.
- Modules loading assets on every page. A popup module that fires only on the homepage but ships 150KB of JS on every product page is pure waste. Scan your waterfall for module asset paths where they shouldn't be.
- Hero sliders. 3–5 high-res images plus a JS carousel library, often 1–5MB total, for a component fewer than 1% of users interact with past slide one. We replace these with a single static hero on almost every redesign.
- Themes shipped without performance review. Full Bootstrap for three components, unminified bundles, no image dimensions, synchronous scripts. Ask theme developers for a Lighthouse score on the demo before you buy.
- Missing indexes. A query with the right index runs in 10ms; without one, the same query at peak traffic runs for 5s and queues every request behind it. You won't notice until Black Friday.
Performance monitoring checklist
Quick checks (5 minutes)
- PageSpeed Insights on homepage, category, product
_PS_MODE_DEV_isfalse- Smarty not on "Force compilation"
- CCC enabled
- OPcache active per
phpinfo()
Monthly maintenance (30 minutes)
- Clean abandoned carts older than 3 months
- Clean
ps_log,ps_connections,ps_connections_page,ps_guest - Run
OPTIMIZE TABLEon the cleaned tables - Review the slow query log
- Uninstall any unused modules
Quarterly review (2 hours)
- Full GTmetrix test from multiple locations, compared to last quarter
- Review Core Web Vitals in Google Search Console
- Audit module assets for unnecessary CSS/JS
- Check image sizes for oversized uploads
- Review server resource usage (CPU, RAM, disk I/O)
- Test on a real mid-range mobile device, not Chrome's emulator
- Verify browser caching headers are still in place
- Check PHP error logs (exceptions burn resources even when nobody sees them)
After every deployment
- Clear PrestaShop cache
- Reset OPcache web pool if
validate_timestamps=0 - Quick PageSpeed test for regressions
- Check the browser console for JS errors
- Walk the checkout end-to-end
Where to start
Four things, in order: fix server config (OPcache + InnoDB buffer pool + PHP-FPM), regenerate and serve images at the right sizes in WebP, uninstall modules you don't use, clean up the database. That covers the bulk of the performance problems we find on audits.
Measure before and after every change. Keep a short changelog of what you did and what the numbers did. The optimisation that matters is the one your customers feel: faster product pages, a responsive checkout. That translates into trust, lower bounce and higher conversion.
Related reading
- PrestaShop Hosting Guide: Requirements, Server Setup & Performance — server configuration for optimal speed
- PrestaShop Troubleshooting: Fix White Screens & 500 Errors — diagnose performance-related issues
- PrestaShop SEO: URLs, Schema, Sitemaps & Core Web Vitals — Core Web Vitals tie performance to SEO
- Performance Tuning Your PrestaShop Store: From Database Queries to Full Page Cache
- Performance Revolution — our own module: full page cache, Critters critical CSS, PurgeCSS, invalidation handled for you