Repository navigation
fix: honor cache TTLs while protecting private responses - #52
Conversation
brianvarskonst
left a comment
There was a problem hiding this comment.
Verdict: The logic is correct; two small things should land before merging. CI is green, including "Native Nginx config integration", so nginx -t accepts the new regex.
What's right
- Root cause is correct:
fastcgi_no_cachetreats any value other than empty or"0"as "don't cache", so a positiveX-Accel-Expireshas blocked caching entirely. - The new guard is needed: a positive
X-Accel-Expiresoverrides Nginx's nativeCache-Control: private/no-store/no-cachechecks. - I ran the regex against sample values. It matches
private,Private,public,no-store,public , private,no-cache="Set-Cookie"and WordPress' 404 header. It does not matchx-private,no-store-foo,max-age=0,public, max-age=60(theNotFoundCacheSubscribervalue). ResponseCacheabilityalready only blocks onx-accel-expires:0, so Nginx now behaves the way the diagnostics already report.
Findings
-
Medium – Missing CHANGELOG entry with an upgrade note. Fix PRs so far carry their own CHANGELOG entry (#48, 738969d). It matters here: anyone who regenerates only the FastCGI include gets
unknown "sympress_cache_skip_cache_control" variablefromnginx -t. That fails loudly rather than silently, but it still breaks the deploy. The "regenerate the HTTP and FastCGI includes together" note currently lives only in the PR description. -
Medium – No test checks the behavior of the regex that protects private responses. The unit test only asserts that the
map $upstream_http_cache_control …line exists. The Nginx integration test only validates syntax, because its fixture has no upstream and$upstream_http_cache_controlis always empty there. If the regex regresses later,privateresponses get cached and nothing notices; the PR description only lists manual Nginx/FPM checks. Cheap fix: inNginxConfigGeneratorTest, assert the exact regex line and run a smallpreg_matchtable against the same pattern with the cases above. -
Low / question – Repeated
Cache-Controlheaders on Nginx < 1.23. As far as I know,$upstream_http_*only returns the first header there; combining repeated headers came with 1.23. On Nginx 1.22 (Debian 12) with a positiveX-Accel-Expires, aprivatein a second header could be missed. Repeated headers are rare from PHP, but the README documents no minimum Nginx version. Either document one or explicitly accept the residual risk.
Not verified: behavior in a real Nginx/FPM setup (relying on the checks listed in the PR description); the Nginx version detail in point 3 is from memory. After merging, pages that previously sent a positive X-Accel-Expires will be cached for that TTL — intended, but it may be noticeable on production sites.
The map guarding private responses was only checked for existence. Run the generated regex against private and public directive samples so a broken pattern cannot silently let private pages into the cache.
The FastCGI include now references the Cache-Control map from the HTTP include, and Nginx before 1.23 only exposes the first repeated upstream Cache-Control header to that map.
Responses with a positive
X-Accel-Expiresvalue currently never enter the FastCGI cache: the generator passes the header directly tofastcgi_no_cache, where any nonzero value forbids storage.Remove that argument and let Nginx apply the header's native TTL semantics. Add an explicit response Cache-Control guard for
private,no-cacheandno-store, because a positive X-Accel-Expires otherwise takes precedence over those native checks. A zero value still disables caching, and the existing skip/cookie protections remain. Update the relevant assertions in the existing generator test; no new test infrastructure, workflows or DevOps files.Local validation: the updated test failed before the fix. All 115 PHPUnit tests (571 assertions) passed in the existing PHP 8.5 container with Sodium. Real isolated Nginx/FPM requests using freshly generated configuration confirmed normal and positive-60-second responses go MISS → HIT, while zero TTL, private/no-store responses and Set-Cookie remain MISS → MISS. The same protection was verified with positive TTL combined with private/no-store, no-cache and cookies, including mixed-case directives, qualified private values and repeated Cache-Control headers. Coding standards, hook documentation and static analysis passed. Host
composer qaencountered a missing Sodium extension; its full suite passed in the container with that extension.Consumers need to regenerate both HTTP and FastCGI Nginx includes together after installing the fixed plugin release, because the FastCGI include references the new response-header map. This PR does not change deployed configuration or run cache purges.