Why WordPress speed matters
Google uses load speed as a ranking factor. Every 100 ms of delay cuts conversion by 1%. Below are 10 concrete steps.
10 ways to speed up WordPress
| # | Method | Speed gain | Difficulty |
|---|---|---|---|
| 1 | Page cache (LiteSpeed/WP Rocket) | ★★★★★ | Low |
| 2 | CDN (Cloudflare / BunnyCDN) | ★★★★☆ | Low |
| 3 | WebP images | ★★★★☆ | Low |
| 4 | Image lazy loading | ★★★☆☆ | Low |
| 5 | Redis Object Cache | ★★★★★ | Medium |
| 6 | CSS/JS minification | ★★★☆☆ | Low |
| 7 | Deferred JS (defer) | ★★★☆☆ | Low |
| 8 | Database tuning (WP-Sweep) | ★★☆☆☆ | Low |
| 9 | PHP 8.2 + OPcache | ★★★★☆ | Medium |
| 10 | GZIP/Brotli compression | ★★★☆☆ | Low |
Enabling OPcache in php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=0
opcache.validate_timestamps=0 ; production only!
GZIP in Nginx
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 256;
gzip_comp_level 6;
Common errors when speeding things up
Turning on aggressive caching and PHP tuning sometimes breaks the site instead of speeding it up. Here is what specific error texts mean and how to fix them.
| Error text or symptom | Cause | What to do |
|---|---|---|
White screen after setting opcache.validate_timestamps=0 | OPcache keeps the bytecode of the old file version in memory and does not see the new deployment | Call opcache_reset() or restart php-fpm after every deployment |
| There has been a critical error on this website | The JS minifier merged incompatible theme and cache-plugin scripts into one file | Disable JS minification for the conflicting file only and check debug.log |
| Error establishing a database connection after enabling object cache | The Redis or Memcached extension is unavailable on PHP-FPM, not the database itself | Check php -m | grep redis and the status with systemctl status redis-server |
| Layout "jumps" after Lazy Loading | The browser cannot reserve space for an image without width and height attributes | Set real width and height on img tags, not just CSS sizes |
For details on setting up Redis itself for WordPress, see the article on Redis object cache.
Limits: when cache and a CDN will not help
Cache-based speedups do not work for every page type or on every hosting plan.
- Pages with personal content — account area, cart, checkout form — cannot be cached as a whole: every user would see someone else's data
- On plans with a hard CPU limit (most shared hosting) you cannot run a Redis object cache — there is no access to systemd or your own services
- If a site gets fewer than 100 visitors a day, setting up a CDN will not pay off the time spent — the bottleneck is usually in the code, not in static asset delivery
- Sites with active WebSocket connections or AJAX long-polling do not get faster from page caching, because every request is unique
- Heavy PDF catalogs and on-the-fly report generators will still hit the CPU on the first request; caching only hides the problem for repeat visits
Before turning on all ten methods at once, measure a baseline: without a baseline TTFB and Core Web Vitals number you cannot prove that a specific change actually helped rather than coinciding with a quieter time of day on the host.
Resource priority and Critical CSS
After the ten basic steps, the next level is moving the critical styles into head and loading the rest of the CSS asynchronously. This directly affects Largest Contentful Paint.
<!-- In head: inline critical styles -->
<style>/* only above-the-fold styles */</style>
<!-- The rest of the CSS — async -->
<link rel="preload" href="/wp-content/themes/mytheme/style.css" as="style" onload="this.rel='stylesheet'">
How to check that the optimization worked
PageSpeed Insights numbers vary from run to run, so verify with several methods at once.
# Check time to first byte
curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n' https://example.com/
# Check that compression is served
curl -sI -H 'Accept-Encoding: gzip, br' https://example.com/ | grep -i content-encoding
- Content-Encoding in the response is br or gzip, otherwise compression was not applied
- An X-Cache: HIT header, or the equivalent from your cache plugin, on the second request for the same page
- PageSpeed Insights: Largest Contentful Paint under 2.5 seconds on mobile
- GTmetrix: TTFB under 200 ms on hosting with NVMe and PHP OPcache
If PHP is still the bottleneck after all these steps, check the OPcache settings for PHP, and check compression at the web server level in the article on GZIP and Brotli.
Frequently asked questions
Can cart and account pages be cached in WooCommerce?
No, pages with personal content — the cart, account area, checkout — can't be cached like ordinary static pages, since one shopper would end up seeing another shopper's data. Caching plugins usually exclude such URLs by default, but it's worth checking the settings manually to confirm that the store's dynamic sections are actually flagged as exceptions in the caching rules.
Why can a site show a white screen specifically after turning off opcache.validate_timestamps?
With validate_timestamps disabled, OPcache stops comparing files against disk and keeps the old version's bytecode in memory even after new files are deployed. If the new code isn't compatible with that stale cached state, the server returns an error instead of a page. The fix is resetting the cache or restarting PHP-FPM right after every file update in production.
Is setting up a CDN or Redis worth it for a site with very little traffic?
For a site with very few daily visitors, setting up a CDN usually doesn't pay back the time spent — the bottleneck is more often the code itself than static asset delivery. Redis isn't always an option either: on plans with a hard CPU limit there's simply nowhere to run an object cache, since there's no access to background services.
Why can the layout "jump" after turning on lazy loading for images?
The browser only reserves space for a picture ahead of time when the img tag has explicit width and height attributes. If only CSS sizing is set, or the attributes are missing, the browser doesn't know the image's proportions until it loads, so nearby content shifts right after it appears — felt as a sudden layout "jump."