Skip to content

Fix fastcgi cache serving stale content for unpublished pages - #1642

Merged
retlehs merged 3 commits into
masterfrom
fix/fastcgi-cache-stale-drafts
Mar 5, 2026
Merged

retlehs merged 3 commits into
masterfrom
fix/fastcgi-cache-stale-drafts

Conversation

@retlehs

@retlehs retlehs commented Mar 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Make fastcgi_cache_use_stale fully configurable via the nginx_cache_use_stale variable
  • Default to error timeout invalid_header http_500 (without updating), so nginx waits for the fresh upstream response instead of serving stale cached content when pages are unpublished/drafted

Related: #1641 addresses cached redirects causing loops (#1594)

Closes #1551

Breaking change

This changes the default caching behavior. Previously, nginx served stale cache entries while background updates fetched fresh content (fastcgi_cache_use_stale updating). The new default favors correctness (immediate fresh responses) over latency during cache refreshes.

To restore the previous behavior:

nginx_cache_use_stale: updating error timeout invalid_header http_500

Test plan

  • With default nginx_cache_use_stale: publish a page, visit it, unpublish → should return 404 immediately after cache expires (default 30s)
  • With updating added back: confirm stale-while-revalidate behavior is preserved

🤖 Generated with Claude Code

Remove `updating` from `fastcgi_cache_use_stale` by default so nginx
waits for the fresh upstream response instead of serving stale cached
content. This prevents unpublished/drafted pages from continuing to be
served from cache indefinitely.

Add `nginx_cache_use_stale_updating` variable (default: false) so users
who prefer stale-while-revalidate behavior can opt back in.

This is a behavior change: previously nginx served stale cache entries
while background updates fetched fresh content. The new default favors
correctness (fresh responses) over latency during cache refreshes.

Closes #1551

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@retlehs

retlehs commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

CI failure here is expected: this is an XPASS(strict) from the template regression tests after the fix in this PR.

The test is still marked xfail(strict=True), so pytest intentionally fails when it starts passing.

Cleanup is tracked in #1648, which removes the xfail markers once this PR is merged.

@retlehs

retlehs commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

Local verification

Branch tested: fix/fastcgi-cache-stale-drafts
Environment: Trellis local development VM (Lima)

Setup

Temporary config: cache.enabled: true in group_vars/development/wordpress_sites.yml

Config diff

Before (master):

69:  fastcgi_cache_use_stale updating error timeout invalid_header http_500;

After (fix/fastcgi-cache-stale-drafts):

68:  fastcgi_cache_use_stale error timeout invalid_header http_500;

Test 1: nginx_cache_use_stale_updating: false (new default)

Publish a page, prime cache, unpublish, observe:

# Create and prime cache
wp post create --post_type=page --post_title="Trellis 1642 Test" --post_status=publish --porcelain
# => 1226
curl -sI http://example.test/trellis-1642-test/  # MISS
curl -sI http://example.test/trellis-1642-test/  # HIT
curl -sI http://example.test/trellis-1642-test/  # HIT

# Unpublish
wp post update 1226 --post_status=draft

# Check behavior
curl -sI http://example.test/trellis-1642-test/  # 200 HIT (still cached)
curl -sI http://example.test/trellis-1642-test/  # 404 Not Found
curl -sI http://example.test/trellis-1642-test/  # 404 Not Found

Once the cache entry expires, nginx fetches fresh from PHP and returns 404.

On master (before fix), the same test produces:

curl -sI http://example.test/trellis-1642-test/  # 200 STALE
curl -sI http://example.test/trellis-1642-test/  # 200 STALE
curl -sI http://example.test/trellis-1642-test/  # 200 STALE

Stale 200 persists indefinitely — unpublished content keeps being served.

Test 2: nginx_cache_use_stale_updating: true (opt-in)

Set nginx_cache_use_stale_updating: true in group_vars/development/main.yml, re-provision.

$ trellis vm shell -- sudo nginx -T | grep "fastcgi_cache_use_stale"
fastcgi_cache_use_stale updating error timeout invalid_header http_500;

Same publish/unpublish test:

curl -sI http://example.test/trellis-1642-test-opt-in/  # 200 HIT
curl -sI http://example.test/trellis-1642-test-opt-in/  # 200 STALE
curl -sI http://example.test/trellis-1642-test-opt-in/  # 200 STALE

Stale-while-revalidate behavior is preserved when explicitly opted in.

Summary

Scenario After unpublish
master (updating hardcoded) 200 STALE indefinitely
fix, default (updating off) 200 HIT404 Not Found
fix, opt-in (updating on) 200 HIT200 STALE (expected)

Conclusion: Verified locally — updating removed from fastcgi_cache_use_stale by default. Unpublished pages stop serving stale 200 once cache expires. Opt-in via nginx_cache_use_stale_updating: true preserves previous behavior.

@retlehs retlehs self-assigned this Mar 4, 2026
@retlehs
retlehs requested a review from swalkinshaw March 4, 2026 20:10
Comment thread roles/nginx/templates/nginx.conf.j2 Outdated
Replace the boolean `nginx_cache_use_stale_updating` toggle with a
`nginx_cache_use_stale` variable containing the full directive value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@retlehs
retlehs merged commit 89dd077 into master Mar 5, 2026
1 of 2 checks passed
@retlehs
retlehs deleted the fix/fastcgi-cache-stale-drafts branch March 5, 2026 04:47
retlehs added a commit that referenced this pull request Mar 5, 2026
Align test with #1642 which replaced the boolean
nginx_cache_use_stale_updating with a full-value nginx_cache_use_stale
variable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enabling caching prevents previously published pages being unpublished by changing to draft

2 participants