Get CV

DevOps

nginx cache that must not see a logged-in page

A 10 second HTML microcache in shared memory is cheap. It is also wrong the moment auth_token is on the request. Bypass on the cookie, not on a guess.

Vue SSR sits behind nginx. After a deploy, anonymous GET of a notes page is the same HTML for ten seconds. I put that in fastcgi_cache with an 8 MB keys zone. Node CPU dropped. Then a stranger got my admin shell for those ten seconds, because the cache key was only the URL.

Sanctum keeps the session in the httpOnly cookie auth_token. The HTML for a logged-in user is not a private article body. It is a different header, a different menu, a different empty state. One cached page with that markup is enough. nginx served it as a HIT to the next anonymous visitor.

Users on the public site saw an editor chrome they should never see. I saw it as a cache that could not tell a cookie request from a public one.

Cookie on the request must skip the shared HTML cache.
Cookie on the request must skip the shared HTML cache.

Bypass on the cookie

The rule is one line in nginx: if the request has auth_token, skip the cache. Do the same for the CSRF cookie on POST. I do not cache /api at all. JSON from LaraBoom is already in Redis when it is worth caching, and a cached 401 is worse than a slow 401.

The zone lives in the nginx worker, not in Redis. That is the point. Redis already holds list payloads. nginx holds the rendered shell. Mixing them means a cache purge has to talk to two stores, and I would forget one of them.

I logged $upstream_cache_status on the notes location. After login the same URL had to show MISS or BYPASS, never HIT. A HIT with my name in the body meant the key still ignored the cookie. Vary on Cookie would have split the zone into one entry per session. Bypass is cheaper and does not store private HTML at all.

Cache only public GET HTML. JSON stays with PHP.
Cache only public GET HTML. JSON stays with PHP.

What I measure

fastcgi_cache_bypass and fastcgi_no_cache both need the same condition. Bypass on read, no_cache on write. If only one is set, a logged-in response still lands in the zone and the next anonymous visitor can get it. That was the bug: I had bypass on, no_cache off, and my shell wrote a public key.

After a login I hit the same URL twice, once with the cookie and once without. The first must miss and go to Node. The second, without the cookie, may hit. If the HIT body contains my name, the bypass is wrong.

TTL stays at 10 seconds. Longer than that, a publish in admin sits behind nginx after Redis already expired. Shorter than that, the zone does no work. I also skip cache on anything that is not GET. A HEAD from a probe must not populate HTML. Query strings on the public notes list are part of the key only when they change the page. Tracking params must not.

What I took from this

I measure cache status with and without auth_token on the same URL. A HIT that contains a display name is a leak, even at 10 seconds.

I do not trust a cache key that is only the path. Bypass and no_cache must share the cookie condition. Vary on Cookie stores private pages. Skip the zone instead.

The rule I keep: cookie requests never write a public HTML key. JSON stays with PHP and Redis. nginx caches anonymous GET shells only.

Back to notes