From ae4becc220a997acb3596461ef7c6e42bf9a2de2 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Wed, 3 Jun 2026 09:00:37 +0100 Subject: [PATCH 1/2] nginx: honour X-Force-Refresh header for per-request cache bypass The previous $force_refresh map keyed off $request_uri with a single ${FORCE_CACHE_REFRESH_ON_REQUEST} default -- meaning every URI mapped to the same env-var value. No per-request control. Useful only as a global toggle (currently false). After v1.14.x VFBquery releases we need a way to overwrite canonical cache slots for the (query_type, entity_id) matrix in the background so end-users never pay the cold-miss latency. Combined with proxy_cache_use_stale updating + proxy_cache_background_update (already on), this gives stale-while-revalidate semantics plus an explicit warmup hook. Wire shape: map $http_x_force_refresh $force_refresh { default ${FORCE_CACHE_REFRESH_ON_REQUEST}; "~*^(true|1|yes)$" 1; } `proxy_cache_bypass $force_refresh;` is unchanged downstream and critically `proxy_no_cache` is NOT set, so the upstream response from a force-refreshed request overwrites the canonical cache entry -- subsequent normal users get the fresh body. Header-not-query-param is deliberate: the cache key includes the URI so a `?force_refresh=true` query param would land the fresh response in a different cache slot than the slot normal users read from. Header has zero effect on the key. Companion: owlery-cache-reload --force-refresh (separate PR) sets this header on every request during the post-release warmup pass. Operational note: owl_cache should sit behind the VFB firewall and v3-cached.virtualflybrain.org is only reachable from the internet via the Informatics LBs. If external exposure for X-Force-Refresh ever becomes a worry, gate the header inside the existing $is_whitelisted_ip machinery in this file. Not done here -- the overhead of an extra map for a clearly-internal-only tool is more mechanism than the threat warrants today. --- nginx.conf.template | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nginx.conf.template b/nginx.conf.template index 342333d..dfc7e35 100644 --- a/nginx.conf.template +++ b/nginx.conf.template @@ -71,8 +71,17 @@ http { proxy_cache_min_uses 1; proxy_cache_revalidate on; proxy_cache_methods GET HEAD; - map $request_uri $force_refresh { + # Per-request force-refresh: an `X-Force-Refresh: true|1|yes` header on a + # request bypasses the cache and writes the upstream response into the + # canonical cache slot (proxy_cache_bypass without proxy_no_cache). Used + # by the post-release VFBquery warmup tool to overwrite v3-cached entries + # for every query-type x entity-id tuple, so users never see a cold miss. + # FORCE_CACHE_REFRESH_ON_REQUEST remains as a global "treat every request + # as a refresh" nuclear toggle and supplies the default when the header + # is absent. + map $http_x_force_refresh $force_refresh { default ${FORCE_CACHE_REFRESH_ON_REQUEST}; + "~*^(true|1|yes)$" 1; } proxy_cache_valid 200 ${CACHE_STALE_TIME}; From e357521265cc24c6d2bccf419f59cb28cd1e2261 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Wed, 3 Jun 2026 10:04:17 +0100 Subject: [PATCH 2/2] only allow whitelist to update Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- nginx.conf.template | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nginx.conf.template b/nginx.conf.template index dfc7e35..051f2b1 100644 --- a/nginx.conf.template +++ b/nginx.conf.template @@ -71,17 +71,17 @@ http { proxy_cache_min_uses 1; proxy_cache_revalidate on; proxy_cache_methods GET HEAD; - # Per-request force-refresh: an `X-Force-Refresh: true|1|yes` header on a - # request bypasses the cache and writes the upstream response into the - # canonical cache slot (proxy_cache_bypass without proxy_no_cache). Used - # by the post-release VFBquery warmup tool to overwrite v3-cached entries - # for every query-type x entity-id tuple, so users never see a cold miss. - # FORCE_CACHE_REFRESH_ON_REQUEST remains as a global "treat every request - # as a refresh" nuclear toggle and supplies the default when the header - # is absent. - map $http_x_force_refresh $force_refresh { + # Per-request force-refresh: trusted callers (whitelisted IPs) may send an + # `X-Force-Refresh: true|1|yes|on` header to bypass the cache and write the + # upstream response into the canonical cache slot (proxy_cache_bypass without + # proxy_no_cache). Used by the post-release VFBquery warmup tool to overwrite + # v3-cached entries for every query-type x entity-id tuple, so users never see a cold miss. + # FORCE_CACHE_REFRESH_ON_REQUEST remains as a global "treat every request as a refresh" + # nuclear toggle and supplies the default when the header is absent (or the client is not + # whitelisted). + map "$is_whitelisted_ip:$http_x_force_refresh" $force_refresh { default ${FORCE_CACHE_REFRESH_ON_REQUEST}; - "~*^(true|1|yes)$" 1; + "~*^1:(true|1|yes|on)$" 1; } proxy_cache_valid 200 ${CACHE_STALE_TIME};