#!/usr/bin/env bash # cachecheck.sh — did the purge actually evict anything? # # THE TRAP THIS EXISTS FOR: a PURGE request that returns 200 has proved that # SOMETHING answered, not that a cache dropped anything. On a Cloudways app, # a PURGE aimed at the public URL sails past the cache tier and WordPress # answers it with a normal page — 200, every time, evicting nothing. Four # "successful" purges in a row cleared exactly zero objects. # # The only honest test is a differential one: ask for the bare URL, then ask # for the same page with a cache-busting query string. The bare URL is what a # visitor and a crawler receive. The busted URL is what the origin generates # right now. IF THEY DISAGREE, A CACHE IS SERVING A DOCUMENT YOUR SERVER NO # LONGER PRODUCES — and no status code anywhere will tell you that. set -u URL="${1:?usage: cachecheck.sh }" UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/126 Safari/537.36' T=$(mktemp -d); trap 'rm -rf "$T"' EXIT sep="${URL#*\?}"; q="?"; [ "$sep" != "$URL" ] && q="&" curl -s -A "$UA" -D "$T/h1" -o "$T/bare" "$URL" curl -s -A "$UA" -D "$T/h2" -o "$T/bust" "${URL}${q}cachecheck=$(date +%s)" b=$(wc -c < "$T/bare" | tr -d ' '); f=$(wc -c < "$T/bust" | tr -d ' ') age=$(grep -i '^age:' "$T/h1" | tr -d '\r' | awk '{print $2}') xc=$(grep -i '^x-cache:' "$T/h1" | tr -d '\r' | awk '{print $2}') prov=$(grep -i '^cache-provider:' "$T/h1" | tr -d '\r' | cut -d' ' -f2-) echo " $URL" echo " bare: ${b}B origin-now: ${f}B age=${age:-none} x-cache=${xc:-none} ${prov:+provider=$prov}" # ⚠️ THE TOOL'S OWN FIRST RUN PRODUCED A FALSE POSITIVE: the cache-busted # request was challenged and came back 163 bytes, so the script reported the # cached copy as 36,504% stale. A comparison is only valid when BOTH sides are # real pages — guarding one side and trusting the other is the same mistake as # trusting a 200. for side in bare bust; do n=$(wc -c < "$T/$side" | tr -d ' ') if [ "$n" -lt 2000 ] || { [ "$n" -lt 20000 ] && grep -qiE 'one moment, please|being verified|checking your browser' "$T/$side"; }; then echo " X the $side reading is ${n}B — a challenge or an error page, not the site." echo " Cannot compare. This vantage point is being refused; measure elsewhere." exit 3 fi done # A few bytes of nonce differ between any two renders; a stale cache differs by # whole blocks. 2% is the line between noise and a different document. diff=$(( b > f ? b - f : f - b )) pct=$(( f > 0 ? diff * 100 / f : 0 )) if [ "$pct" -le 2 ]; then echo " OK cache is serving what the origin generates (${pct}% apart)" [ -n "${age:-}" ] && [ "${age:-0}" -gt 3600 ] 2>/dev/null && \ echo " ! but age=${age}s — every change you make stays invisible that long" exit 0 fi echo " X STALE: ${pct}% apart. A cache is serving a document the origin no longer produces." echo " A purge that returned 200 did not evict this. Flush the OBJECT cache and the" echo " hosting cache from wp-admin, not by sending PURGE to the public URL." exit 1