Most WordPress performance advice focuses on assets. Compress images, minify CSS, defer JavaScript, etc. You’ve seen them. And, those things matter, but they’re not the layer that ultimately decides whether your site scales.
Headers do.
Response headers are the instructions that tell browsers, CDNs, and proxies how they’re allowed to treat your content. They determine whether a response can be reused, whether multiple variations exist, and how long something may live before it needs to be refreshed.
This is post is an adaption of a lesson in my Make WordPress Fast course, the most complete course on all aspects of WordPress Performance Optimization out there.
Before we talk about specific headers, it helps to understand what headers actually are. Every time a browser requests something from a server, the request and the response contain two parts. The first part is the visible content, like HTML, JSON, images, or CSS. The second part is a set of small metadata instructions called headers. These headers travel along with the request and response and tell the systems involved how the data should be handled.

Headers don’t contain the content itself. Instead, they describe the rules around that content. They can tell a browser whether it’s allowed to cache a page, tell a CDN how long it may store a response, tell proxies whether multiple versions of a response might exist, or tell the browser how the payload was compressed.
In other words, headers don’t change what your site sends, they change how that response behaves across the entire delivery chain. This is why headers matter so much for performance. They are the control layer that determines whether responses can be reused, when they must be refreshed, and how many variations of a response might exist. Understanding them means understanding how your site actually behaves once it leaves the server.
You can have a perfectly optimized page, a fast server, and a well-configured CDN. If the headers contradict your caching strategy, the infrastructure cannot help you.
The key to understanding headers is realizing they answer only three performance questions.
Can this response be reused?
How many versions of it exist?
How long may it live before it needs to be refreshed?
Once you look at headers through those three questions, they stop feeling like protocol trivia and start looking like a control panel.
So let’s talk about them.
First: Decide Whether the Response Is Allowed to Be Cached
The first job headers perform is determining whether reuse is allowed at all. The directive that controls this is Cache-Control.
Cache-Control can allow shared caches such as CDNs to store a response, restrict it to browser-only caching, or forbid storage entirely. A typical response might look like this:
Cache-Control: public, max-age=300
The keyword public allows shared caches to store the response. If the directive instead says private, a CDN must not store it, even if the page itself appears identical for every visitor.
Then there is the most restrictive directive: no-store.
Cache-Control: no-store
This tells every cache layer not to store the response anywhere. Not in the browser. Not in intermediary proxies. Not at the edge.
It is effectively the nuclear option for caching.
This is where many WordPress sites accidentally undermine their own performance. A page that should be reusable across visitors returns a private or no-store directive because a plugin sets conservative defaults.
The CDN obeys the instruction, and suddenly the request travels all the way back to the origin for every visitor. At that point, it doesn’t matter how good your infrastructure is. The response has been declared non-reusable.
There is also an important distinction between browser caching and shared caching.
The max-age directive defines how long the browser may reuse the response. The s-maxage directive defines how long shared caches such as CDNs may reuse it.
If you don’t differentiate between those two intentionally, you lose precision. You might cache aggressively in the browser but barely at the edge, or the opposite.
Understanding which cache layer you’re instructing is the first step toward real control.
Next: Decide How Many Versions of the Response May Exist
Once a response is allowed to be cached, the next question becomes how many variations of that response might exist. That is controlled by the Vary header.
Vary tells caches what aspects of the request determine whether the response should be treated as unique. A common example looks like this:
Vary: Accept-Encoding
This tells caches that the response differs depending on whether the client supports gzip or brotli compression. That’s normal and expected. But variation can quickly spiral out of control.
If a response includes something like this:
Vary: User-Agent
The cache now treats every browser type as potentially unique. Since user agents vary widely, the number of possible versions multiplies quickly.
Even more dangerous is the directive we discussed in the previous module section:
Vary: Cookie
This tells caches that every cookie combination might produce a different response.
Now imagine what that means in a WordPress environment where multiple plugins set cookies.
Instead of one cached page, you may end up with dozens or hundreds of variants. The cache technically still works, but reuse becomes rare. This phenomenon is called cache fragmentation.
Caching may be enabled, but the cache hit ratio collapses because the system is storing too many variations. The hygiene rule here is straightforward: only vary on dimensions that genuinely change the output.
Every additional variation reduces reuse, and reuse is the foundation of scalable performance.
Then: Decide How Long the Response May Live
Even if a response is eligible for caching and does not create excessive variations, the system still needs to know how long the response may remain valid.
This is the lifetime question.
Cache-Control directives such as max-age and s-maxage define that lifetime.
Cache-Control: public, max-age=300, s-maxage=600
In this example, the browser may reuse the response for five minutes, while shared caches such as CDNs may reuse it for ten minutes.
By the way, it’s possible you will also encounter the Expires header in this context:
Expires: Wed, 21 Oct 2026 07:28:00 GMT
Expires is the older mechanism for defining expiration. Modern systems primarily rely on Cache-Control instead. If both are present, they must agree. Conflicting directives create unpredictable behavior across browsers and proxies.
Short lifetimes increase freshness but reduce reuse. Long lifetimes increase reuse but may delay updates.
The goal is to align the lifetime of the cache with the volatility of the content.
What Happens When Cached Content Becomes Stale
Eventually, cached responses expire. At that point, the system must decide whether to regenerate the content or simply confirm that nothing has changed. This is where revalidation mechanisms come in.
Two headers enable this behavior: ETag and Last-Modified.
An ETag might look like this:
ETag: "abc123"
When a browser or CDN has a cached response, it can ask the origin server a conditional question.
“Has this resource changed since I last saw it?” If nothing has changed, the server responds with:
304 Not Modified
No payload is transferred. The cache simply continues using its existing copy. That’s efficient, but it only works when the validation signals are stable.
If ETags are generated differently across multiple origin servers, caches cannot reliably compare them. If Last-Modified timestamps change unnecessarily, caches revalidate even when the content hasn’t actually changed.
Revalidation is cheaper than regenerating the entire response, but it still involves a round trip. At scale, those round trips accumulate.
Payload Handling: Making the Response Efficient to Deliver
Beyond caching behavior, headers also influence how the payload itself is delivered. The Content-Encoding header tells the browser how the response body was compressed.
Content-Encoding: br
This might indicate brotli compression. If textual responses such as HTML, CSS, or JavaScript are served without compression, unnecessary bandwidth is consumed.
But compression must be consistent across the stack. If both the origin and the CDN compress responses independently without coordination, the result can be inconsistent caching behavior.
The Content-Type header determines how the browser interprets the payload.
Content-Type: text/html
Incorrect types can break preloading behavior, prevent caching optimizations, or interfere with early rendering strategies. These headers may seem minor, but they affect how efficiently browsers process and render the response.
Security Headers and the Performance Tradeoff
Modern sites often include additional headers related to security policy. Content-Security-Policy defines which resources a page may load. Permissions-Policy controls access to certain browser capabilities. Strict-Transport-Security enforces HTTPS.
These headers improve security posture, but they also require careful tuning. A very strict Content-Security-Policy might block third-party scripts that would otherwise load asynchronously. In some cases, that improves performance. In others, it breaks functionality or forces complicated workarounds.
The key principle is alignment. Headers should reflect deliberate system design, not the accidental accumulation of plugin defaults.
Diagnosing Header Behavior in Practice
Understanding headers conceptually is useful, but real control comes from observing them. Open your browser’s developer tools and inspect the main document request, and then ask three questions.
- Is this response eligible for shared caching?
- How many variations does this response create?
- How long is the response allowed to live?
Then compare different contexts.
Look at the site while logged out and while logged in. Compare the homepage with a product page. Compare an anonymous browser session with one carrying cookies.
You want to see intentional differences.
The checkout page might legitimately bypass caching. The homepage probably should not. Once you start reading headers this way, performance debugging becomes faster.
Instead of staring at a waterfall and guessing, you immediately classify the problem.
Is caching disallowed?
Is variation excessive?
Is lifetime too short?
Is revalidation constant?
Those questions lead directly to the root cause.
Why Header Hygiene Matters
Headers are not decorative metadata. They are the instructions that define how every layer of the web stack behaves. If those instructions contradict your caching strategy, your caching strategy does not exist.
Once you understand that, performance optimization stops being a series of tweaks. It becomes the deliberate design of how your site is allowed to behave across browsers, CDNs, and origin servers.


Leave a Reply