SEO check

Page Caching: What It Is, How to Test It and How to Turn It On

Page caching serves a saved copy of your HTML instead of rebuilding it for every visit. Learn how to test it, switch it on and what our report checks.

Free to use 16 min read Updated Oct 2026

What is page caching? The quick answer

Quick answer: Page caching saves the finished HTML of a page after your server builds it once, then hands that stored copy to the next visitors so PHP and the database stay idle. Our report does not score the page cache itself, only CSS, JavaScript and image caching headers, so the steps below test the page cache by hand and the labels describe that manual test.

  • Pass: In the manual test, the repeat request shows a cache hit, such as HIT in x-cache or cf-cache-status or an Age header above 0, and it answers clearly faster than the first one.
  • Warning: In the manual test the repeat request is no faster than the first, even though your CSS, JavaScript and images carry caching headers.
  • Fail: Every request runs the whole application, so no header ever reports a hit and the response time never drops.
  • Fix: Switch on one page cache at the host, plugin or server level, exclude logged-in and cart pages, then send two requests and read the headers.

Check my website

What is page caching and how does it work?

Page caching, also called full page caching or server-side caching, stores the complete HTML that a page produced so later requests for the same URL receive the stored copy instead of a fresh build. A dynamic site such as WordPress, WooCommerce or a Laravel app runs PHP and several database queries for each visit, yet for anonymous visitors the output is usually identical, so rebuilding it is wasted work.

The request path is short. A visitor asks for a URL, and the cache looks it up by a key built from the host, the path and often the query string. If a usable copy exists, the cache returns it straight away. If not, the application builds the page, sends it and saves a copy for a set lifetime, known as the TTL or time to live.

The cache can live in several places. A plugin can write static files to disk, the web server can hold them (LiteSpeed LSCache or the Nginx FastCGI cache), a reverse proxy cache such as Varnish can sit in front, or an edge network can keep copies closer to the visitor. The MDN HTTP caching guide explains the header vocabulary.

Cache hit versus cache miss

A hit means the stored copy is fresh. A miss means nothing usable is stored, so the page is built, sent and saved. The first visit after any purge is always a miss, which is why preloading matters, and a copy past its TTL is rebuilt or revalidated with a conditional request that can return 304 Not Modified.

What exactly is stored

Only the rendered HTML document is stored. Images, stylesheets and scripts have their own caching rules, so a site can have perfect asset headers and still rebuild its HTML on every request.

Page cache vs browser cache, object cache and CDN cache

People use the word cache for at least five different layers, and mixing them up leads to the wrong fix. The table separates them.

LayerWhat it storesWhere it livesWho gainsHow to spot it
Page cacheThe whole HTML documentServer, plugin or proxyEvery anonymous visitorA status header, or a footprint comment in the source
Browser cacheFiles, and sometimes pagesThe visitor's own deviceThat visitor, on repeat views onlyCache-Control and ETag on each file
Object cache (Redis, Memcached)Query results and computed fragmentsServer memoryLogged-in and other uncached requestsNo response header, only faster dynamic pages
Opcode cache (OPcache)Compiled PHP codeServer memoryEvery request that executes PHPPHP settings page, often already on
CDN or edge cacheStatic files, and HTML only when a rule allows itServers near the visitorVisitors far from your servercf-cache-status or a similar edge header

The browser layer is what the image caching check, the JavaScript caching check and the CSS caching check look at, so the header detail lives on those pages. For the definition of an edge network and the rule our report applies to it, read the CDN usage test.

Why page caching matters for speed, crawling and server load

The biggest gain is time to first byte. A stored page can leave the server in a few milliseconds, while a PHP and database build often takes hundreds, and every later timing, including Largest Contentful Paint, starts from that first byte. WordPress Site Health treats a median server response under 600 ms as good, a rough target.

Capacity is the second benefit. A viral post or campaign email can send thousands of visitors in minutes, and a page cache absorbs most of them, which on a small plan can mean a fast spike instead of a 503 error.

Crawling benefits too, because a quick, steady server lets Googlebot fetch more pages per visit. For more, read our page load time check, the Core Web Vitals guide and the article on how hosting speed affects rankings.

A page cache has limits. It does not shrink a 3 MB hero image or speed up a slow chat widget, and speed is only one of many ranking inputs, so treat caching as a foundation rather than a ranking lever.

How SEO Site Checker checks caching on your page

What we test: The main report has no row that scores the server-side page cache. It loads your page in headless Chromium and parses the rendered HTML. Other checks, such as HTTP/2, HSTS and mixed content, fetch the URL again, but none compares two responses or reads cache status headers or Age.

What the report checks today

For CSS and JavaScript, the report takes the first 8 stylesheets and first 8 scripts, requests each with a 3 second timeout, and counts a file as cached when the response carries a Cache-Control or an Expires header. The CSS caching and JavaScript caching pages explain the detail.

For image caching, it sends a HEAD request for up to 15 images. An image counts as uncached when its Cache-Control contains no-cache or max-age=0, or when the request fails.

Two caveats apply. The CSS and JavaScript rows look for the header only, not its value, so no-store still passes, while the image row treats a missing header as cached. A file that misses the 3 second timeout counts as uncached, so a slow host can lower these rows even with fine headers.

What it does not check

The report does not tell you whether the HTML itself came from a cache. Hit or miss status, the Age value, the TTL and any bypass rules are all outside its view, which is why the next section gives you a manual two request test.

What green, amber and red mean

This key applies to the report's asset rows only, not to the manual test above. A present header is the only thing proven, because it can still carry a poor lifetime.

  • Pass: Every checked CSS and JavaScript file has a header, and for images none of the checked files is uncached.
  • Warning: Only some CSS or JavaScript files have a header, or fewer than 20 percent of the checked images are uncached.
  • Fail: None of the checked CSS or JavaScript files has a header, or 20 percent or more of the checked images are uncached. A page that links no stylesheets or scripts also turns red.
  • Not tested: Whether the HTML came from a page cache, including hit or miss status, Age, TTL and bypass rules.

Run the free report on my site

How to check whether your page cache is working

This test works whichever layer holds the cache.

  1. Test as an anonymous visitor. Use curl or a private browser window, because logged-in views are normally not cached.
  2. Request the URL twice. Send a GET and discard the body, as in the command below. Avoid curl -I, because a HEAD request can show MISS on caches that only fill from GET. The first answer may say MISS and the second should say HIT.
  3. Read the status headers. In curl output or the DevTools Network tab, look for cf-cache-status (HIT, MISS, DYNAMIC, BYPASS or EXPIRED), x-cache, x-cache-status, x-litespeed-cache, x-proxy-cache, x-srcache-fetch-status and x-varnish. Two numbers in x-varnish mean a hit, and an Age header above 0 shows a shared cache served a stored copy.
  4. Look for a footprint if no header appears. Plugin caches that serve static files, such as WP Super Cache, WP Rocket and W3 Total Cache, may send no hit header, depending on their settings. Read the last lines of the HTML in view-source, where they usually leave a comment with a generation time. An identical time across two loads means the copy was reused.
  5. Read Cache-Control on the HTML. A positive max-age or s-maxage lets caches reuse the page. no-store keeps every cache from storing it, and private keeps shared caches (proxies and CDNs) from storing it while still letting the visitor's browser do so. A line such as no-cache or max-age=0 means browsers must revalidate, and some page caches send it on purpose while still serving hits, so trust the cache status header or the timing gap over this line.
  6. Compare timing. Time three requests with the curl -w option and the time_starttransfer variable. A hit is usually far quicker than a miss, and when the headers are silent this gap is the fallback proof.
  7. Try WordPress Site Health. On WordPress 6.1 or newer, open Tools, then Site Health. Its page cache test requests your home page three times, looks for caching headers, compares the median response time with 600 ms and counts an advanced-cache.php drop-in. Treat it as a hint, since a lone ETag or Last-Modified header can fool it.

Two requests, two answers

Text
for i in 1 2; do
  curl -s --compressed -o /dev/null -D /dev/stdout https://example.com/ | grep -iE '^(cache-control|age|x-cache|x-cache-status|x-litespeed-cache|cf-cache-status|x-proxy-cache|x-srcache-fetch-status|x-varnish)'
  echo
done

On a cold cache the first pass typically prints a line such as x-cache-status: MISS or cf-cache-status: MISS. The second pass shows HIT, and an Age line may appear with a small number of seconds. A missing status line does not prove there is no cache, since header names vary by layer.

For the timing comparison, purge the page first and run this loop before anything else has requested it, because the header test above has already warmed the cache.

Text
for i in 1 2 3; do
  curl -s --compressed -o /dev/null -w "%{time_starttransfer}\n" https://example.com/
done

As an illustration, timings of 0.62, 0.04 and 0.03 right after a purge show a miss followed by hits, while three values near 0.6 mean the page is rebuilt each time.

How to turn on page caching on your site

  1. Find the layer you control. That may be a host panel switch, a plugin, your own Nginx or Varnish, or an edge rule on your CDN.
  2. Enable exactly one full page cache. A host cache, a plugin and a CDN rule that all store HTML give you mixed headers and slow purges, because each layer keeps its own copy and publishing empties only one of them.
  3. Write the exclusions first. The bypass rules for cart, checkout, account and logged-in visitors, listed under what not to cache, must exist before launch, or one visitor's personal page can reach another.
  4. Set a lifetime that fits your content. Match the TTL to how often pages change, and purge on every update.
  5. Preload the key URLs. Warming the cache spares the first real visitor the miss.
  6. Retest. Run the two request check from the previous section, then follow the guide below that matches your stack.

Page caching on WordPress, LiteSpeed, Nginx and Cloudflare

Pick the layer you control, and each block names its proof.

WordPress

Install one caching plugin or use the cache built into your host. Most add an advanced-cache.php drop-in to wp-content and set the following line in wp-config.php, which is what Site Health looks for.

PHP
define( 'WP_CACHE', true );

Switch the cache on, keep it off for logged-in users and enable preload. On WooCommerce, add the exclusions from the section on what not to cache. Proof is the footprint comment at the end of the page source or the timing gap from the test above.

LiteSpeed and Apache

On LiteSpeed hosting, enable LSCache through the LiteSpeed Cache plugin and look for x-litespeed-cache: hit. Plain Apache can use mod_cache with mod_cache_disk and CacheEnable disk /, but a plugin or proxy is usually safer for PHP sites. Apache stores a 200 response only when it carries Last-Modified, ETag, Expires or a Cache-Control max-age, and PHP output often has none of them, so you would also need CacheIgnoreNoLastMod On with a CacheDefaultExpire, or a Cache-Control header sent by the app. CacheDisable matches paths only, so for a cookie bypass set the no-cache environment variable with SetEnvIf Cookie, and remember mod_cache_disk also needs a CacheRoot directory. CacheHeader on adds an X-Cache header that reports HIT, MISS or REVALIDATE.

Nginx FastCGI cache

The sample is an excerpt, not a complete server block, and assumes your PHP location already has its root and index lines. Including $request_method in the key keeps HEAD and GET responses in separate cache entries. The cache directory, /var/cache/nginx here, must exist and be writable by the nginx user.

Text
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PAGES:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

server {
    set $skip_cache 0;
    if ($request_method = POST) { set $skip_cache 1; }
    if ($query_string != "") { set $skip_cache 1; }
    if ($request_uri ~* "/wp-admin/|/wp-login.php|/cart|/checkout|/my-account") { set $skip_cache 1; }
    if ($http_cookie ~* "wordpress_logged_in|comment_author|wp_woocommerce_session_|woocommerce_items_in_cart") { set $skip_cache 1; }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_cache PAGES;
        fastcgi_cache_valid 200 301 302 60m;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
        add_header X-Cache-Status $upstream_cache_status;
    }
}

Skipping on any query string is the safe default, but a campaign heavy site should instead strip utm_* and fbclid from the key, for example by mapping $args to a cleaned variable, or its tagged landing pages never get cached. The skip variable bypasses the cache for POST requests, query strings, account and checkout paths, and logged-in or cart cookies, and the X-Cache-Status header exposes HIT or MISS as your proof. Nginx also refuses to cache a response that carries Set-Cookie or Vary: *, and a Cache-Control header sent by PHP overrides fastcgi_cache_valid, so check what PHP sends and run nginx -t before each reload. The Nginx FastCGI module reference documents each directive.

Cloudflare

By default Cloudflare caches only static file extensions, so cf-cache-status shows DYNAMIC for your HTML pages until a Cache Rule says otherwise. In the dashboard, open Caching, then Cache Rules, and create a rule for your HTML paths set to Eligible for cache with an Edge TTL. Then add a Bypass cache rule below it for requests whose cookie contains the logged-in or cart cookie, for example wordpress_logged_in, because when two Cache Rules conflict the last matching rule wins. The second request should then show cf-cache-status: HIT. If it still shows DYNAMIC or never reaches HIT, check whether the origin sends Set-Cookie or a no-store or private Cache-Control on the HTML, because Cloudflare will not store those responses. The Cloudflare default cache behavior page explains the defaults. An HTML cache rule without that cookie bypass can show one visitor's personal page to another, so test it logged in and logged out, and purge when content changes.

Shopify, Wix and Squarespace

Hosted builders run their own caching and expose no page cache switch. The curl test still works, but your real lever is weight: remove unused apps and leftover script snippets, and compress large images.

What not to cache and how long to keep a cached page

  • Personal and session pages. Cart, checkout, account, order confirmation and any page with a personal greeting must never be cached. Bypass the cache on the matching cookie.
  • Requests that change state. Skip POST requests, admin and API paths, search results and preview URLs. Ignore marketing parameters such as utm_source and fbclid in the cache key, so one page is not stored dozens of times.
  • A sensible lifetime. Stable pages can live a day or more, while news or price pages suit minutes to a few hours. Purge on every publish, update and price change.
  • Successful responses only. Keep errors such as 404, 500 and 503 out of the cache, or keep their life short.
  • Separate mobile copies only when the HTML differs. Responsive CSS needs one cached copy, but a server that returns different markup per device needs a Vary header or a device based cache key.
  • Careful Vary headers. Send a Vary header of Accept-Encoding for gzip or brotli. Avoid Vary: Cookie on pages meant to be shared, since every cookie value becomes its own copy.

Page caching mistakes that break sites

Worst failure: A visitor sees another visitor's cart or account page. It happens when the cookie bypass is missing, and it is the one mistake that does real harm. Test it logged out and logged in before you call the cache finished.

  • Stale content after an edit. The old HTML stays until the TTL ends or something purges it. Add purge hooks for updates, or shorten the lifetime.
  • Two page caches stacked. Two plugins can also rewrite the same advanced-cache.php drop-in, so keep one cache, as step two of the setup list says.
  • A cookie on every first visit. Some consent, A/B testing or personalization scripts set a cookie before the page loads. A bypass rule that matches any cookie then skips the cache for everyone, and Vary: Cookie stores a separate copy per visitor.
  • A no-store header on every page. A plugin or theme that sends no-store or private makes a hit impossible, and the response headers show it.
  • No purge after an update. A theme update can rename CSS files while old cached HTML still points at the previous names, and the layout breaks until you purge.

How to Fix

The short version, the same steps you see when you click How to Fix in your report.

Why this fails: The server builds the full page again for every visit, because nothing stores the finished HTML. That means a slower first byte and more hosting load.

How to tell: Request the page twice with a GET that throws the body away. If no cache status header (x-cache, cf-cache-status, x-litespeed-cache or x-proxy-cache) says HIT and Age never rises above 0, the HTML is probably not coming from a cache. Plugins that send no header are the exception, so also check the page source footer for a cache comment or compare time_starttransfer across runs. Test as a visitor, because logged-in views are normally not cached.

Text
curl -s --compressed -o /dev/null -D /dev/stdout https://example.com/ | grep -iE '^(age|x-cache|x-cache-status|cf-cache-status|x-litespeed-cache|x-proxy-cache|x-varnish)'

How to fix it:

  1. Choose one layer you control: a host cache setting, a caching plugin, an Nginx FastCGI cache or an edge rule.
  2. Switch on page caching and exclude the cart, checkout, account and login pages.
  3. Set a lifetime, add a purge on publish and preload your key URLs.
  4. Clear the cache and load the page twice.

If you set headers yourself, max-age controls browsers and s-maxage controls shared caches. A purge cannot reach a browser that already holds the copy, so keep max-age short for HTML. A cache friendly line looks like this:

Text
Cache-Control: public, max-age=300, s-maxage=3600

Platform shortcuts:

  • WordPress: use one caching plugin or the host cache, never two.
  • LiteSpeed hosting: enable the LiteSpeed Cache plugin.
  • Nginx: use the fastcgi_cache directives with a bypass for cookies.
  • Apache: mod_cache is possible, but a plugin or proxy is simpler.
  • Cloudflare: add a Cache Rule, because HTML is not cached by default.
  • Shopify, Wix and Squarespace: the platform handles it, so there is nothing to switch on.

Re-test: The second curl request should show HIT or a rising Age, and time_starttransfer should drop across three runs. Then run our free report again to see the CSS, JavaScript and image caching rows.

Re-test my website

Frequently Asked Questions

What is page caching?
Page caching, also called full page caching, stores the finished HTML of a page after it is built once and serves that copy to later visitors. The server skips PHP and database work, so responses arrive faster and hosting load drops, especially during traffic spikes.
How do I know if page caching is working?
Request the page twice with curl as a GET that discards the body, or read the response headers in browser DevTools. Look for HIT in x-cache, cf-cache-status, x-litespeed-cache or x-proxy-cache, or an Age header above 0. Some plugin caches send no such header, so also check the end of the page source for a footprint comment or compare response times across three runs.
How do I enable page caching in WordPress?
Use one caching plugin or your host's own cache, switch on page caching, exclude the cart, checkout and account pages, and keep caching off for logged-in users. Then request the page twice with curl, or look for the plugin's footprint comment in the page source. Do not run two page cache plugins together, because they fight over the same drop-in and purges.
Is page caching the same as browser caching?
No. A page cache keeps whole HTML copies on the server or a proxy and serves every visitor from them. Browser cache keeps files on one visitor's device, so only repeat visits gain, steered by Cache-Control, ETag and Expires. Fast sites use both layers together.
Does page caching help SEO?
Indirectly. It lowers response time and server strain, which supports Core Web Vitals and steady crawling, but it is not a ranking switch. Caching that serves stale or personal pages can hurt, so test after every change.
What pages should you not cache?
Anything that differs per visitor or changes state should stay out of the cache: logged-in views, cart, checkout, account, login, admin, previews and form results. Use cookie and URL rules to bypass the cache for them, and send private or no-store on those responses so no shared cache keeps a copy.
How do I clear the page cache?
For the site cache, purge it from your caching plugin, host panel or CDN dashboard. For the browser cache, use a hard refresh or clear site data in the browser. They are separate layers, so a big change may need both.
Why does my site still feel slow with caching on?
The cache may be bypassed by cookies, query strings or a no-store header, so check the response headers first. Otherwise the delay comes from heavy images, render blocking scripts and third party tags, which a page cache does not touch.

See how your site scores right now

Paste your address into the free report, then use the How to Fix button on anything that needs work.

Free to use. Results in under a minute for most sites.