-
Notifications
You must be signed in to change notification settings - Fork 0
Page Cache
GT Performance stores a rendered page as a file and serves it back from advanced-cache.php, before WordPress boots. A hit costs one config read, one hash, and two file reads.
wp-settings.php
└─ advanced-cache.php ← the drop-in
├─ read wp-content/cache/gt-performance/config.json.php
├─ build a RequestContext from $_SERVER / $_COOKIE
├─ Eligibility::decide() ← may bypass here
├─ CacheKey::hash()
└─ serve pages/<xx>/<hash>.html → exit
└─ (miss) WordPress loads normally
└─ PageCacheModule captures the response and stores it
Nothing in the hit path touches the database or the plugin autoloader.
Checked in this order by Eligibility. The first failure wins and becomes the bypass reason:
| Check | Bypasses when |
|---|---|
cache_disabled |
Origin caching is off |
method |
Not GET or HEAD
|
host_missing |
No Host header |
authorization |
An Authorization header is present |
signed_bypass |
X-GT-Performance-Bypass is present |
path:… |
Path matches a bypass path, on segment boundaries |
query:… |
A query parameter is on the bypass list |
unknown_query:… |
Any query parameter that is not on the ignored list |
cookie:… |
A cookie name starts with a bypass prefix |
That unknown_query rule is the important one and it surprises people: an unrecognised query parameter bypasses the cache rather than creating a new cache entry. This is deliberate. It means an attacker cannot fill your disk by requesting ?a=1, ?a=2, ?a=3, and it means a parameter your site actually uses can never be silently ignored into a wrong hit. Add parameters you want ignored (stripped from the key, still cached) to ignored_query_params.
Defaults:
-
Ignored (cached, parameter dropped from the key):
fbclid,gclid,gbraid,msclkid,utm_campaign,utm_content,utm_medium,utm_source,utm_term -
Bypassed:
add-to-cart,customize_changeset_uuid,elementor-preview,fluent-cart,preview,s,wc-ajax,gtperf_verify,gtperf_css_preview -
Bypass paths:
/wp-admin/,/wp-login.php,/wp-cron.php,/wp-json/,/xmlrpc.php -
Bypass cookies:
comment_author_,wordpress_logged_in_,wordpress_no_cache,wp-postpass_
Commerce adapters add their own paths, cookies and parameters on top. See Commerce Safety.
scheme | host | path | sorted-query | variant | generation
hashed with SHA-256. variant is public, or mobile when Separate mobile cache is on and the user agent matches Mobile|Android|iPhone|iPad. generation is a counter you can bump to invalidate everything at once without deleting files.
The drop-in and WordPress build this key through the same code, and a test asserts they agree byte for byte. If they ever diverge, every page misses forever — or worse, the drop-in reaches a different bypass decision than WordPress and serves a cached page to a signed-in visitor.
Even on an eligible request, the rendered response is validated before it is written:
| Reason | Condition |
|---|---|
status:NNN |
Anything other than HTTP 200 |
not_html |
Empty body, or no <!doctype html> / <html>
|
set_cookie |
The response sets any cookie |
private_cache_control |
Cache-Control contains no-store or private
|
content_type |
Content-Type is not text/html
|
donotcachepage |
DONOTCACHEPAGE is defined and truthy |
The set_cookie rule is what stops a personalised response leaking. If a plugin starts a session or sets a preference cookie while rendering, that page is not stored — no configuration required.
Three windows, all configurable:
| Setting | Default | Meaning |
|---|---|---|
fresh_ttl |
3600s | Served as HIT
|
stale_ttl |
86400s | Past fresh, served as STALE while a rebuild is queued |
browser_ttl |
300s |
max-age sent to the visitor's browser |
stale_if_error |
86400s | Emitted as stale-if-error for shared caches |
A stale entry is still served instantly. The preload queue rebuilds it in the background, so visitors never wait for a regeneration. A request carrying X-GT-Preload deliberately skips the stale copy and falls through to WordPress, which is how a preload actually refreshes content instead of reading its own stale output.
| Header | Values |
|---|---|
X-GT-Cache |
HIT, STALE, MISS, EXPIRED, REVALIDATE, BYPASS, DYNAMIC
|
X-GT-Cache-Reason |
The bypass reason, only when Debug mode is on |
X-GT-Cache-Key |
First 12 characters of the key hash |
ETag |
SHA-256 of the stored body; conditional requests get a 304 |
Age |
Seconds since the entry was stored |
BYPASS and DYNAMIC reason headers are suppressed unless Debug mode is enabled, so a production site does not advertise its bypass rules. If you are debugging and see no X-GT-Cache header at all, that is a bypass with debug off.
| Trigger | Default behaviour |
|---|---|
| Publishing or updating a post | Post URL, home, its archives and feeds (related) |
| Comment posted, edited, approved, deleted | That post's URL |
| Menu updated, theme switched, customizer saved | Everything |
| Manual | Admin bar, Purge GT cache button, or WP-CLI |
Post-publish behaviour is switchable between Post URL only, Related pages and Entire page and edge cache, or automatic purging can be turned off entirely.
Every purge produces a receipt recording what was requested and what was actually evicted, including the Cloudflare result. See Diagnostics.
wp-content/cache/gt-performance/
├── config.json.php ← compiled cache config (inert JSON, see Architecture)
├── redis-config.json.php ← Redis runtime config
├── pages/<xx>/<hash>.html ← cached body
├── pages/<xx>/<hash>.meta.json
├── assets/ ← generated CSS/JS/fonts (publicly reachable by design)
├── locks/
└── logs/
pages/, locks/ and logs/ get an index.html and a deny-all .htaccess. assets/ stays reachable because the browser must fetch generated CSS and JS from it.
Entries are written to a temporary sibling and renamed into place, so a request can never read a half-written page.
Cache Exclusions · Commerce Safety · Private Islands · Architecture · Diagnostics
GT Performance · GPL-2.0-or-later · Issues and pull requests welcome
Getting started
Caching
Optimization
Delivery
Operations
Reference