I Thought My Blog Was Cached. It Was Not

Since the day it launched I had assumed this blog was cached, because it runs on LiteSpeed and LiteSpeed is famous for caching. On 20 September 2026, while measuring my own sites, I ran curl -sI against the home page, looked at the headers, and could not find a single one that had anything to do with caching. Not a hit, not a miss, not an age, nothing. Every request was going straight through PHP.

This post is the notebook for that afternoon: what I found, how I measured it, what the measurement cannot tell you, and what I am going to change now. There is no before-and-after here, because the “after” has not happened yet. When it has, the numbers will go at the bottom of this page, gathered exactly the same way, whether or not they flatter me.

Diagram of five caching layers from browser cache down to the database, showing the hit and miss paths.

What actually came back

Here is the interesting part of the response from this blog’s home page on 20 September 2026:

  • Server: LiteSpeed – the web server is what I thought it was.
  • X-Powered-By: PHP/8.3.33 – a current PHP, which is good news and also a clue: PHP is answering, so PHP is running for this request.
  • platform: hostinger and panel: hpanel – the shared platform announcing itself.
  • content-encoding: br – Brotli compression is on, which is one thing I did not have to fix.
  • alt-svc advertising h3 – the server is offering HTTP/3.
  • No x-litespeed-cache header. No cache status of any kind.

The same was true of my second blog on the same plan. LiteSpeed the web server and LiteSpeed Cache the WordPress plugin are two different things, and I had quietly conflated them. The server was there. The cache was not, because nothing was installed to populate it.

One important limit on that finding, which I want stated before anyone quotes me: this is about my sites on that day, not about the host. A different account on the same platform with the caching plugin configured would return a completely different set of headers. The correct sentence is “my two blogs returned no page-cache header on 20 September 2026”, not “Hostinger does not cache”.

How I measured it, in enough detail for you to argue with me

The method matters more than the numbers, so here it is in full. I wrote a small shell script that requests each URL five times, about two seconds apart, and records DNS, connect, TLS, time to first byte, total time and bytes downloaded. It ran from home fibre in Valencia, Spain, on 20 September 2026, using curl 8.19.0 with the Schannel TLS backend. I publish the median of the five, plus the minimum and the maximum, because an average hides exactly the thing I found most interesting.

Every URL was requested in two modes. The first is the plain URL, which is what a visitor gets. The second appends ?cb= followed by a nanosecond timestamp, a cache-buster: a unique query string that no cache can have seen before, which forces the request to be built at the origin.

That second mode is the whole experiment. On a site with a working page cache, the plain URL should come back noticeably faster than the cache-busted one, because one is a file being handed over and the other is PHP assembling a page from a database. If the two numbers are the same, there is no cache in the middle.

The numbers, before I change anything

SiteTTFB, plain URLTTFB, cache-bustedMin to max, plainHTML size
This blog (WordPress, shared)0.455 s0.397 s0.379 – 0.895 s100,574 bytes, 26,886 with Brotli
vireltify.com (WordPress, shared)0.568 s0.485 s0.534 – 1.500 s99,141 bytes, 26,324 with Brotli
creativowebvlc.com (static, same account, served through the host CDN)0.294 s0.265 s0.243 – 0.703 s56,325 bytes, 2,482 with Brotli
Medians of five requests per site and per mode, Valencia, Spain, 20 September 2026. Three different sites with different content: the third row is a contrast, not a control.

Read the first two rows carefully, because they are backwards from what most people expect. On both WordPress blogs the cache-busted request was faster than the plain one. Not slower. That is not a mystery: if nothing is cached, both requests do identical work, and the difference between 0.455 and 0.397 is network noise, not caching. If there had been a page cache, the plain column would have been dramatically lower than the cache-busted one. It was not, on either site, in either direction.

The third row is my own agency site, on the same shared account, but served through the host’s CDN, which answers with Server: hcdn instead of LiteSpeed. It is roughly 0.16 s quicker to first byte than this blog. I am not going to claim that gap is caused by the CDN, because it is also a completely different page: 56 KB of very repetitive HTML that Brotli squeezes to 2.4 KB, against 100 KB of WordPress output that compresses to 27 KB. Different application, different content, different everything. What I take from it is a question for later, not a conclusion, and I will go after it properly in do you need a CDN, and what changed on my sites.

The number I find hardest to ignore is in the fourth column. vireltify’s time to first byte went from 0.534 s to 1.500 s across five consecutive requests on an idle site. Nearly three times, with nobody on it. That spread is what “every request rebuilds the page on shared hosting” looks like from outside.

What these numbers are not

I want to be blunt about this, because this is the section most speed articles skip and it is the reason most of them are useless.

  • This is not page speed. I measured the HTML document of one page. No CSS, no JavaScript, no images, no fonts. Nothing here says anything about how long the page takes to become usable in a browser.
  • This is not Core Web Vitals. There is no LCP, no CLS and no INP in this data, and you cannot derive them from it. Those need a different tool and real field data, and they get their own post in Core Web Vitals on a cheap shared plan.
  • This is one route on one afternoon. Spain to a server, over my own broadband, through whichever peering my provider felt like using. The audience for this blog is mostly in the United States and the United Kingdom, and their numbers would be different. Do not read these as what a reader in Chicago sees.
  • Five requests are not a load test. None of my sites has meaningful traffic, so there is nothing here about behaviour under load, and I will not be writing that sentence until there is.
  • I did not test HTTP/3. The server advertises it in alt-svc. That is all I know. My curl build does not include nghttp2, so it could not even negotiate HTTP/2; I confirmed HTTP/2 support separately by reading the ALPN result from an openssl s_client handshake. Anyone writing “I tested HTTP/3” needs to say with what.

What I am changing, in order

One change at a time, re-measuring between each, because a batch of five simultaneous changes tells you nothing about which one mattered.

  1. Install and configure the caching plugin that matches the server. On LiteSpeed that is LiteSpeed Cache, and the success criterion is not a score, it is a header: x-litespeed-cache: hit on the second request. If that header does not appear, nothing has been achieved regardless of what any dashboard says.
  2. Re-run the same script, same five requests, same two modes. The test that matters is whether the gap between the plain and cache-busted columns opens up. If the plain URL does not get faster than the cache-busted one, the cache is not being served.
  3. Measure the front end separately, with the right tool. PageSpeed Insights, on named URLs, on a named date, reported as lab data and labelled as such.
  4. Audit the plugins, afterwards. A cache is a much bigger lever than removing a plugin, and I would rather know what the cache did on its own first. The list of what I keep and why is in the plugins I keep, and the one I just removed.
  5. Then, and only then, look at the CDN question raised by that third table row.

Notice what is not on that list: moving host. My blogs are slow to first byte because they are doing work they should not be doing, not because the plan is bad, and moving would have carried the problem with it. The plan itself is the one I describe in my review of the account these sites run on.

Three things I am not going to do

Install a second optimisation plugin. Two caching layers fighting each other is the classic way to turn a fixable problem into an unfixable one.

Publish a before-and-after gathered a different way. If the second measurement uses a different tool, a different connection or a different number of requests, the comparison is decoration. Same script, same place, same five requests, or it does not go on the page.

Chase a score. The target is a cache header and a visible drop in time to first byte on the plain URL. If a scoring tool disagrees afterwards, that is a separate conversation with separate evidence.

Run the same check on your own site in two minutes

You do not need my script. Three commands, on any machine with curl:

# 1. Is anything cached at all?
curl -sI https://yoursite.com | grep -i -E 'cache|age'

# 2. Time to first byte, plain URL
curl -s -o /dev/null -w '%{time_starttransfer}\n' https://yoursite.com

# 3. Time to first byte, cache-busted
curl -s -o /dev/null -w '%{time_starttransfer}\n' "https://yoursite.com/?cb=$(date +%s%N)"

Run the second and third a few times each, not once. If the two figures sit on top of each other, you are in the position I was in: the plugin page says caching is enabled, and the server says otherwise. Believe the server.

The reason I am writing this before fixing it rather than after is that the embarrassing version is the useful one. Anybody can publish the tidy chart at the end. The part worth reading is that a person who writes about hosting had a caching plugin he never installed, on two sites, and found out with a command that takes four seconds to type. The update goes at the bottom of this page when the header changes.