Most slow WordPress sites are slow for the same few reasons: no page cache, oversized images, too many plugins, an old PHP version, and a pile of scripts loaded from other people's servers. None of them is exotic. What matters is the order you tackle them in, because the first two usually deliver most of the gain and the rest are tuning.
It also matters to stop guessing. A site can feel slow for three quite different reasons: the server takes a long time to start responding, the page is enormous, or the browser is busy running scripts after everything has arrived. Each has a different cure, and fixing the wrong one wastes an evening.
The steps below are in the order I would do them on a site I had just been handed. Make a backup before you begin, change one thing at a time, and measure after each change so you know what actually helped.
1. Measure first
Use a speed test run from a location near your visitors and read the waterfall, which is the chart showing each file as a bar along a time axis. Two numbers matter first. Time to first byte (TTFB) is how long the server takes to start responding, which points at hosting, PHP, the database and caching. Total page weight is how much the browser must download, which points at images and scripts.
You can get TTFB from a terminal in seconds:
curl -o /dev/null -s -w 'ttfb %{time_starttransfer}s total %{time_total}s\n' https://example.com/
Run it three times and take the middle value. As a rough rule, a cached page on decent hosting answers in well under half a second; an uncached WordPress page taking one to three seconds is common, and it is the sign that caching is missing. Browser-based tools also report Core Web Vitals: Largest Contentful Paint (when the main content appears), Interaction to Next Paint (how quickly the page reacts to a click) and Cumulative Layout Shift (how much things jump around). Write the numbers down. You will want them for comparison.
2. Turn on page caching
This is usually the biggest single improvement. Without a cache, every visit makes PHP load WordPress, run dozens of database queries and build the page from scratch. A page cache saves the finished HTML and hands that same file to the next visitor, so PHP and MySQL do not run at all.
Many hosts provide caching at the server level, often switched on from the panel. Use it if it exists; it is faster than anything running inside WordPress. Otherwise a well-regarded caching plugin does the job. Run only one page cache at a time, because two layers fighting over the same files cause stale pages and confusing purges.
Confirm it works rather than trusting the toggle. Request the same URL twice with curl -sI https://example.com/ and compare: the second response should be faster and may carry a cache-status header. Make sure the cache skips the cart, checkout, account pages and logged-in users. If a page looks stale after you edit it, the troubleshooting guide covers the usual causes.
3. Fix the images
Images are usually most of a page's weight. A page with ten 2 MB photos is slow whatever the server does, because ten photos are 20 MB, and a phone on a mobile connection will take many seconds just to download them.
- Resize to the size displayed. A photo shown at 800 pixels wide does not need 4000 pixels of width. Upload at roughly twice the display width for sharp high-density screens, and no more.
- Compress. Quality around 75 to 85 is usually indistinguishable from 100 and a fraction of the size.
- Serve modern formats such as WebP or AVIF, which are typically much smaller than JPEG at similar quality. Converting by hand:
cwebp -q 80 photo.jpg -o photo.webp. A plugin can do this automatically and fall back to the original for old browsers. - Lazy-load images below the fold with
loading="lazy"; WordPress adds it by default. Do not lazy-load the main image at the top of the page, since that delays the very thing visitors are waiting for. Give itfetchpriority="high"instead. - Always set width and height attributes so the layout does not jump as images arrive.
The page weight calculator shows how quickly the megabytes add up.
4. Be strict about plugins
Each plugin adds code, and sometimes database queries, to every request. The number of plugins matters less than what they do: forty small ones can be lighter than one heavy page builder with an inbuilt slider, forms, popups and analytics. Remove what you do not need. Replace heavy multi-purpose plugins with lighter single-purpose ones.
If you suspect one plugin in particular, profile a staging copy rather than guessing. Install a query-profiling plugin such as Query Monitor, load a typical page, and sort queries by time and by component. A plugin that runs 80 queries or loads scripts on every page for a feature used on one page is the usual culprit. Deactivate it on staging and measure again. If the gain is obvious, you have your answer.
5. Use a current PHP version, then add object caching
Newer PHP releases are noticeably faster, often by a good margin over the 7.x series, and they receive security fixes. Check what you run with php -v or the hosting panel. Before switching, check that your theme and plugins support the version. Test it on staging, change it on live at a quiet time, and watch the error log for a day. Also confirm that OPcache is enabled; it keeps compiled PHP in memory and is normally on by default.
Object caching
If your host offers Redis or Memcached, enabling a persistent object cache reduces repeated database queries by keeping the results in memory. It helps most on pages the page cache cannot serve: logged-in users, the admin area, carts and search. Install the matching plugin, which adds an object-cache.php file to wp-content, and verify with wp redis status or the plugin's status page. On a brochure site where every page is cached, you will see little change, so do this after steps 2 to 5, not before.
6. Tidy the database
Remove post revisions you will not use, expired transients and orphaned data from deleted plugins. Make a backup first, as always.
wp transient delete --expired
wp post delete $(wp post list --post_type=revision --format=ids) --force
wp db optimize
Limit future revisions with define('WP_POST_REVISIONS', 5);. Also check autoloaded options, which WordPress loads on every request: SELECT SUM(LENGTH(option_value)) FROM wp_options WHERE autoload='yes';. A total over a few megabytes suggests a plugin left a large amount behind.
7. Put a CDN in front and mind third-party scripts
A content delivery network serves static files from locations near the visitor and takes traffic off your own server. Get the encryption mode right to avoid redirect loops, and bypass the cache for logged-in and cart requests. Most of the trouble with CDNs comes from caching pages that should stay private, so go slowly with HTML caching.
Third-party scripts
Analytics, chat widgets, web fonts, ad tags and embeds load from other servers and can dominate load time. You cannot make a slow third-party server faster, so keep only what earns its place. Load fonts from your own site, limit them to the weights you use, defer scripts that are not needed at once, and replace embedded video players with a thumbnail that loads the player on click.
A worked example
Take a small shop on shared hosting, with 30 products, a page-builder theme and 22 plugins. The home page takes about 4.5 seconds to become usable on a phone. The waterfall shows a server wait of 1.8 seconds, 6.4 MB of images, and a chat widget, two font families and an analytics tag loading from outside servers.
- Switch on the host's page cache, with the basket, checkout and account pages excluded. The server wait drops to about 0.3 seconds for visitors who are not logged in.
- Convert the oversized hero and product photos to WebP at a suitable display width. Page weight falls from 6.4 MB to about 1.3 MB.
- Remove four plugins that nobody remembers installing, and replace a slider plugin with one static image. Each removal removes a script and a stylesheet from every page.
- Move PHP from 7.4 to 8.2 on staging, test the checkout, then change it on live. Uncached pages such as the basket get faster.
- Replace the chat widget with a plain contact link and host the fonts locally.
After these five changes the same page is usable in about 1.5 seconds. Every figure here is an example, but the pattern is realistic: the cache and the images account for most of the improvement, and the other steps polish the result.
Verifying it
curl -sI https://example.com/ | grep -i -E 'cache|age|server'shows whether a cache header is present on the home page.curl -s https://example.com/ | wc -cgives the size of the HTML alone, before images and scripts.- Your browser's developer tools, Network tab, with "Disable cache" ticked and the filter set to Img, will list every image and its size. Sort by size, and start with the largest.
wp plugin list --status=activecounts the active plugins, andwp option get active_pluginswill show anything the dashboard hides.- Check the
Server-TimingorX-Powered-Byheaders where present, for hints about what is running in front of WordPress. - Repeat the test at different times of day. A site that is quick at 7 am and slow at 3 pm points to shared resource limits rather than to WordPress itself.
What changes by hosting type
| Hosting | Where the gain usually is |
|---|---|
| Shared | Page cache and images; limited control over PHP workers and memory |
| VPS | PHP version, OPcache, object cache; also tuning PHP-FPM workers |
| Dedicated | Same as VPS, plus database tuning |
| Managed WordPress | Server cache is included; focus on images, plugins and scripts |
On shared hosting, slow patches at certain hours often come from neighbours or resource limits; see the Host Talk series for how those limits work.
Questions that come up
Why is my score good but the site feels slow?
Lab tests use one device and one connection. Test on a real phone, over mobile data, and look at the logged-in experience too.
Do I need several optimisation plugins?
No. One page cache and one image tool is plenty. Overlapping plugins undo each other's work.
Will upgrading my hosting plan fix it?
Only if the bottleneck is server capacity. Uncached pages and huge images are slow everywhere.
Checklist
- Baseline recorded: TTFB, page weight and Core Web Vitals.
- Page cache on, verified by a second request, with exclusions for cart and logged-in users.
- Images resized, compressed and served as WebP or AVIF; hero image not lazy-loaded.
- Unused plugins removed; heavy ones profiled on staging.
- Current PHP version and OPcache enabled.
- Object cache on where Redis or Memcached is available.
- CDN set up with correct encryption mode.
- Database tidied after a backup; revisions limited.
- Third-party scripts reviewed and trimmed.
- Measured again and compared with the baseline.