How to Speed Up a Joomla Site : 12 Fixes, 90 Min [2026]
Twelve performance fixes ordered by impact per hour of effort, and an honest account of what to measure instead of chasing a single score.
Your Joomla site takes four seconds to show anything, you were told to “enable caching”, you did, and it made almost no difference. That is the usual experience, because Joomla performance work has a clear order of impact and most advice starts three-quarters of the way down it.
This guide is ordered by impact per unit of effort on Joomla 5.x. Work down and stop when the site is fast enough. Ninety minutes covers the first eight fixes, where nearly all the available gain lives.
Before you change anything: measure the right thing
The most common mistake is optimising toward a single Lighthouse number. It is a weighted composite measured once, on a simulated device, with simulated throttling, and it varies by several points between runs on an unchanged site. Chasing it produces work that improves the score without improving the experience. Measure these instead, and record a baseline first:
| Metric | What it tells you | Where the Joomla work is |
|---|---|---|
| Time to First Byte (TTFB) | How long the server takes to produce HTML | PHP version, OPcache, Joomla cache, database, hosting |
| Largest Contentful Paint (LCP) | When the main content becomes visible | Images, render-blocking CSS/JS, TTFB |
| Cumulative Layout Shift (CLS) | Whether things jump about while loading | Missing image dimensions, late-loading fonts and modules |
| Total page weight | Bytes transferred | Images, template assets, unused extensions |
| Request count | Round trips needed | Unmerged assets, unpublished-but-still-loading modules |
Start with TTFB, because that is where Joomla itself lives. At 900 ms TTFB, no amount of image work will make the site feel fast. On decent shared hosting a cached Joomla 5 page typically returns in 150–400 ms; a tuned VPS often under 200 ms. Far outside that and the server side is your problem — fixes 1, 5 and 6 matter more than everything else combined.
Always measure logged out, in a private window. Joomla bypasses page caching for logged-in users, so an administrator sees the slow, uncached path on every request. Plenty of people have “fixed” a performance problem that only ever affected themselves, and plenty more have concluded caching does nothing because they only ever tested while logged in.
Fix 1: PHP version and OPcache
Highest impact, lowest effort, and routinely skipped because it happens in the hosting control panel rather than in Joomla.
Move to PHP 8.2 or 8.3 and confirm OPcache is on
Joomla 5 requires PHP 8.1 minimum and runs on 8.2 and 8.3. If your site is still on 8.0 or earlier it is both slower and unsupported. In cPanel this is MultiPHP Manager; in Plesk, PHP Settings.
Then check OPcache is enabled: System → System Information → PHP Information and search the page for opcache. You want opcache.enable On.
Reasonable values if you can set them:
opcache.enable = 1
opcache.memory_consumption = 192
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 60
OPcache keeps compiled PHP bytecode in memory instead of recompiling on every request. Joomla touches hundreds of PHP files per page load, so the effect is substantial on any site that lacked it. Note that max_accelerated_files at the default 10000 can be too low for a Joomla site with many extensions, and once exceeded OPcache silently stops caching new files.
Setting opcache.validate_timestamps = 0 squeezes out a little more speed by never checking whether files changed. On a site where you install extensions or apply updates through the Joomla admin, this means your changes appear not to take effect until PHP restarts — a genuinely baffling failure. Leave it at 1 unless you have a deployment process that restarts PHP-FPM.
Fix 2: Joomla’s page cache, and choosing the right one
Joomla has two caching layers and people routinely enable the wrong one. Global Configuration → System → Cache sets the view cache — output of individual modules and component views, with Off/Conservative/Progressive options. System → Plugins → System – Page Cache is a separate thing: full-page caching, storing complete rendered HTML and serving it without running most of Joomla. That is where the large win is.
| Setting | What it caches | Use when | Breaks when |
|---|---|---|---|
| Cache: Conservative | Module and view output, per view | Almost always — the safe default | Rarely |
| Cache: Progressive | As above, but keyed per browser session | Almost never | Cache directory fills with near-duplicate files; on a busy site this makes things slower, not faster |
| System – Page Cache plugin | Entire rendered pages for guests | Content-led sites: brochure, blog, publisher | Any page whose content differs per visitor — carts, dashboards, forms with tokens |
Set Conservative caching and enable Page Cache
In Global Configuration → System: System Cache Conservative, Cache Handler File (or Redis/Memcached if your host provides one), Cache Time 15 minutes to start.
Then enable System – Page Cache in the plugin manager. In its options, set Use Browser Caching to No initially — it sends far-future headers that make debugging your own site maddening — and add any dynamic URLs to the exclusion list.
Progressive deserves its reputation. It keys the cache by session, so every visitor generates a copy of every cached item; on a real-traffic site you get tens of thousands of files, disk I/O rises, and the cache becomes the bottleneck. It exists for heavily personalised module output, and if you are not certain that describes you, it does not.
Full-page caching serves the same HTML to every guest, including any CSRF token embedded in a form. A cached contact or search form will fail validation for visitors who receive a stale token. Add such pages to the plugin’s exclusion list, and test every form on the site after enabling page caching — logged out, in a fresh browser session. This is the single most common way a “faster” Joomla site quietly stops receiving enquiries.
Fix 3: Images
On most Joomla sites images are the majority of page weight, and the gap between an unoptimised and an optimised image library is usually larger than every other fix on this list added together.
Right-size, then re-encode, then serve modern formats
In order:
- Resize to display size. A 4000 px camera JPEG displayed in a 800 px column is wasting roughly 95% of its bytes. This is the biggest single win and no compression setting substitutes for it.
- Re-encode at sensible quality. JPEG quality 75–82 is visually indistinguishable from 100 for photographs at a fraction of the size.
- Convert to WebP or AVIF. WebP typically saves 25–35% over a comparable JPEG; AVIF more again, at the cost of slower encoding.
Joomla 5’s Media Manager does no bulk conversion. Process images before upload, or install an extension that hooks the upload. With shell access, batch conversion is straightforward:
# Resize anything wider than 1600px, in place
find images/ -iname "*.jpg" -exec mogrify -resize '1600>' -quality 80 {} \;
# Produce WebP alongside existing JPEGs
find images/ -iname "*.jpg" -exec cwebp -q 80 {} -o {}.webp \;
Two free wins that are frequently missed: add width and height to every image, since their absence is the commonest cause of a bad CLS; and set loading="lazy" below the fold but not on the LCP image, because lazy-loading the hero delays the exact thing that metric measures.
Fix 4: Unpublish what you are not using
Every published module runs a query and renders; every enabled plugin is loaded and its handlers registered on every request, whether or not it does anything on that page.
Audit modules and plugins
In Content → Site Modules, sort by Status and look at what is published. Modules assigned to positions your template does not have still execute; modules left over from a previous template are a classic find.
In System → Plugins, sort by Type. Look particularly at the system group, which runs on every request. Disable anything you cannot explain.
The usual haul on an older site: editor plugins for editors nobody uses, a leftover slideshow module in an unassigned position, an analytics plugin duplicating a tag already in the template, a search module on a site with no search traffic. Individually small; collectively often worth 100 ms of TTFB and a dozen requests.
Unpublishing stops the code running, which is the performance goal, but leaves the files on disk where they still need patching. If an extension is genuinely finished with, uninstall it — an unpatched, unpublished extension is still an unpatched extension sitting in your web root, and several historic Joomla compromises came through code the owner had forgotten was installed.
Fix 5: Gzip and Brotli compression
Joomla’s Global Configuration → Server → Gzip Page Compression compresses HTML output, typically to a quarter of its size, at negligible CPU cost. Turn it on. Better still, let the web server compress everything — HTML, CSS, JS, SVG, JSON — rather than Joomla’s HTML alone. The shipped htaccess.txt has a mod_deflate block to uncomment. On nginx:
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css text/javascript application/javascript
application/json application/xml image/svg+xml;
If your host supports Brotli, prefer it — it typically compresses text 15–20% better than gzip at comparable speed. Do not enable Joomla’s Gzip and server-level compression on the same content simultaneously; double compression wastes CPU and some configurations produce a broken response.
Fix 6: Database maintenance and session bloat
Joomla’s #__session table is the classic silent performance problem. Every visitor, including every search engine crawler, creates a session row. Joomla garbage-collects them, but the collection is probabilistic and on a low-traffic site can effectively never run, while on a high-traffic site rows accumulate faster than they are cleared.
Check the session table, then move sessions out of the database
Check the size first:
SELECT COUNT(*) FROM `#__session`;
On a normal site this should be tens or low hundreds. Tens of thousands means garbage collection is not keeping up. Clear it safely — this logs everyone out, nothing more:
TRUNCATE TABLE `#__session`;
Then change the handler: Global Configuration → System → Session Handler. If your host offers Redis or Memcached, use it. Sessions are transient data with high write volume; a database is the worst place for them.
Two more tables to check on an older site. #__action_logs grows without limit unless the User Actions Log plugin’s retention period is set — 30 days is reasonable. And #__ucm_history keeps 10 versions of every article by default, which on a large site becomes one of the biggest tables in the database; reduce it under Content → Articles → Options, on the Editing Layout tab, where Enable Versions and the maximum number of versions per item are set.
-- Find your largest tables
SELECT table_name, ROUND(data_length/1024/1024,1) AS data_mb,
ROUND(index_length/1024/1024,1) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length DESC LIMIT 15;
Fix 7: HTTP caching headers
Static assets should be cached by the browser so a second page view does not re-download them. Joomla does not set these headers — the web server does, and many shared hosting defaults do not set them at all.
# .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"
ExpiresDefault "access plus 2 days"
</IfModule>
Long lifetimes are safe for Joomla’s own assets, which carry a version query string that changes with the file. They are not safe for files you overwrite in place: an image replaced at the same path shows the old version to returning visitors for as long as the header says. Change the filename instead.
Fix 8: Template asset bloat
Commercial templates are a common performance liability, because they ship a framework designed to support every layout their demo site offers, and load most of it whether or not you use it.
Audit what the template loads
Open the browser devtools Network tab, filter to CSS and JS, and load the homepage logged out. Look at what arrives and how large it is. Typical findings: a full icon font used for four icons; a slider library on pages with no slider; jQuery loaded twice because a template and an extension each request it; and several megabytes of unused CSS.
What you can do depends on the template. Well-built ones offer per-feature toggles — turn off what you do not use. Joomla 5’s asset manager also lets a template or plugin deregister an asset another extension requested, which is the clean fix for a duplicated library. Do not hand-edit template files; use a child template. And if a template loads 900 KB of CSS with no way to reduce it, replacing it is a legitimate performance fix and often the only real one — Cassiopeia is lean and a reasonable base.
Fix 9: Reduce HTTP requests sensibly
Joomla and various extensions can combine and minify assets. On HTTP/2 and HTTP/3 — nearly all hosting now — combining is much less valuable than it was, because multiplexing removes the per-request penalty that justified concatenation. Minification still helps, since it reduces bytes; combination now mostly helps when you have dozens of tiny files. Test both ways rather than assuming, and note that aggressive combination is the most common cause of “the site broke after I enabled optimisation” — it changes execution order, and scripts depending on that order stop working.
Optimisation extensions typically offer twenty toggles on one screen. Enabling them all and then discovering the site is broken tells you nothing about which toggle did it. Enable one, clear cache, load the site logged out, check the browser console for errors, test a form. Then the next. It is slower and it is the only method that works.
Fix 10: A CDN, when it is actually the answer
A CDN serves static assets from near the visitor. Worth doing when your audience is geographically distant or image bandwidth is large; near useless when audience and server share a country and TTFB is dominated by slow PHP. Full-site proxy CDNs, where all traffic routes through the CDN, additionally give edge HTML caching, TLS termination and DDoS protection, and are usually the better choice. Free tiers suit most small sites; paid plans typically start around €20/month.
Once a proxy CDN caches your HTML, updating an article does not change what visitors see until the edge cache expires or is purged. This surprises people badly. Either configure the CDN to respect origin cache headers and set short HTML lifetimes, or make purging part of your publishing routine. A stale-content complaint that traces back to an unpurged edge cache costs more goodwill than the milliseconds it saved.
Fix 11: Fonts
Web fonts are a common LCP problem and a common CLS problem simultaneously. Three rules cover most of it.
Self-host rather than using a third-party font service: it removes a DNS lookup and TLS handshake to another origin, and a third party from your privacy posture. Subset to the characters you need — a full family with every weight and language range is often 400 KB where a subset is 30 KB. And set font-display: swap so text renders in a fallback immediately instead of staying invisible.
@font-face {
font-family: 'Inter';
src: url('/media/templates/site/mytemplate/fonts/inter-subset.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
Two weights of one family is nearly always enough. Four weights across two families is a design decision with a measurable cost worth knowing before you make it.
Fix 12: Know when the answer is better hosting
There is a floor to what configuration achieves. If TTFB stays near a second after PHP 8.2, OPcache, page caching and a tidy database, the server is the constraint — usually an oversubscribed shared host, a PHP worker limit queueing requests under concurrency, or a contended database server.
| Symptom | Likely cause | Action |
|---|---|---|
| TTFB high and consistent, even cached | Slow or contended CPU | Move host; configuration will not fix this |
| TTFB fine alone, terrible under load | PHP worker limit reached | Upgrade plan, or reduce per-request work |
| Fast HTML, slow assets | No compression or caching headers | Fixes 5 and 7 |
| First view slow, subsequent fast | Cache working as intended | Consider cache warming for key pages |
| Random slow requests, most fast | Cron, backup, or a noisy neighbour | Check scheduled tasks and host resource graphs |
| Slow only for logged-in users | Page cache bypassed by design | Normal — optimise view cache and queries instead |
A ninety-minute running order
- 0–10 min: Record baseline TTFB, LCP, CLS and page weight, logged out.
- 10–20 min: PHP 8.2, verify OPcache, raise
max_accelerated_files. - 20–30 min: Conservative cache on, Page Cache plugin on, test every form.
- 30–40 min: Gzip or Brotli at the server, expires headers.
- 40–55 min: Audit and unpublish unused modules and plugins.
- 55–65 min: Check and truncate the session table; set the action log retention; reduce version history.
- 65–85 min: Images — resize the worst offenders, add dimensions, lazy-load below the fold.
- 85–90 min: Re-measure the same four metrics, logged out, and write both sets down.
Then stop. Fonts, CDN and asset combination are worth doing, but they are refinements on a site that is already fast, and doing them first on a site with a 900 ms TTFB is rearranging furniture in a burning room.
Frequently asked questions
Should I use Conservative or Progressive caching?
Conservative, in essentially all cases. Progressive keys the cache per session, so a busy site generates an enormous number of near-duplicate cache files and often ends up slower than with no caching at all. It exists for heavily personalised module output; if you are not certain that describes your site, it does not.
Why did enabling the Page Cache plugin break my contact form?
Because full-page caching serves identical HTML to every guest, including the CSRF token embedded in the form. Visitors receiving a stale token fail validation. Add form pages to the plugin’s exclusion list and re-test logged out. Always test every form after enabling page caching.
Is a Lighthouse score of 100 worth chasing?
No. It is a weighted composite measured once under simulated conditions, and it varies between runs on an unchanged site. The last fifteen points typically cost more effort than the first eighty and produce changes visitors cannot perceive. Track TTFB, LCP and CLS from real measurements instead, and treat Lighthouse as a hint-generator rather than a target.
Does Joomla need a caching extension, or is core enough?
Core caching plus a sensible server configuration is enough for most content sites. Extensions add value where they do things the core does not — asset optimisation, critical CSS extraction, image conversion on upload. They do not make Joomla’s own cache faster. Get the core settings right before buying anything.
How often should I clear the cache?
Only when content has changed and you need it visible immediately. Routinely clearing the cache defeats it — every clear forces every subsequent visitor to trigger a full uncached render. If you find yourself clearing constantly, your cache lifetime is too long for your publishing rhythm; shorten it rather than clearing by hand.
Will a CDN fix a slow Joomla site?
Only if the slowness is distance or bandwidth. A CDN cannot make PHP execute faster, so a site with a 900 ms TTFB serving a local audience will see almost no improvement. Fix PHP, caching and the database first; then a CDN meaningfully helps a geographically spread audience.
Is it safe to truncate the session table on a live site?
Yes. It logs out everyone currently signed in, including you, and nothing else. Sessions are transient by definition. If the table is large enough that this is a recurring question, move session storage to Redis or Memcached so it stops being a database problem at all.