It was 5:20 on a rainy Tuesday. The house smelled of building work, there was dust on the surfaces, and I had been awake long enough to give up on getting back to sleep. I made a coffee and opened my laptop.
The previous day, a caching problem had taken my website down. It was the second interruption that week, following a Cloudflare outage. I wanted fewer dependencies in the part of the stack responsible for serving my pages, and a setup I could understand well enough to reproduce on another server.
That led me back to NGINX and FastCGI caching.
Working through the configuration
I considered WordPress caching plugins and Redis before deciding to work on page caching at the server. This was a decision about my own setup. I wanted to know where the cached files lived, what caused a request to use them, and how to remove them when necessary.
I read through several guides, connected to my Ubuntu server over SSH and opened the site configuration in Nano. I have been comfortable in a terminal since experimenting with MS-DOS and QuickBASIC as a child. That familiarity helped, but I still needed to understand the configuration in front of me.
The cache zone looked like this:
# Inside the http context, outside any server block.
fastcgi_cache_path /etc/nginx/cache/christophernathaniel levels=1:2 keys_zone=christopher_cache:100m inactive=60m use_temp_path=off;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
Two details matter here. fastcgi_cache_path belongs in the http context. And inactive=60m removes entries that have not been accessed for that period; it does not mean every page expires after an hour. Response freshness is a separate setting. The NGINX FastCGI documentation explains both.
I created the cache directory and set its ownership for the NGINX worker account on my server. Then I started testing the part I actually cared about: opening the website and changing its content.
The first problem was a stale page
I logged into WordPress, edited a page and loaded it again. The page worked, but my changes were missing.
I had expected the cache to behave like the WordPress tools I was used to, particularly when I was logged in as an administrator. That assumption had made it into my setup without being tested. A working page was only the beginning of the check.
I added exclusions for requests that should avoid the shared cache: POST requests, URLs with query strings, administration and login routes, and cookies associated with logged-in users or recent commenters.
For those exclusions to matter, the conditions have to be connected to the cache directives. fastcgi_cache_bypass controls reading a cached response; fastcgi_no_cache controls storing one. A $skip_cache variable on its own does neither. These are separate controls in NGINX.
A cache hit was not the end of the job
I used curl to inspect the response headers. The first request reported:
X-FastCGI-Cache: MISS
The next reported:
X-FastCGI-Cache: HIT
That was satisfying. I could see the cache being populated and then used. But after another content change, the public page still showed the previous version. A hard refresh and an incognito window did not change it.
The server had a saved response. Publishing in WordPress had not, by itself, told that cache to discard it. I cleared the files manually and the new content appeared.
Manual clearing helped me find the problem, but it was not a sensible publishing routine. I tried NGINX Helper to connect WordPress changes to cache clearing. It was an improvement, although I still wanted to check its behaviour rather than assume every update would be handled correctly.
What I took from the morning
The useful result was a better understanding of the system I was running. I knew where the files were, how to recognise a cache hit and why an old page might survive a perfectly successful WordPress save.
I still wanted to explore Redis and object caching, and possibly another way to trigger page-cache clearing. Those were separate pieces of work. For that morning, I had enough: the site was serving pages again, my changes were visible, and I could explain what had happened.
By then the coffee was cold. It was still raining.