Skip to content

About

nginx page cache plugin for ShopClass: keeps pages in nginx's FastCGI cache for an hour, and clears them the moment a listing changes.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

nginx Cache

Hold pages in nginx's FastCGI cache for an hour instead of thirty seconds, and purge them the moment a listing changes.

nginx Cache settings in the ShopClass admin

Verified end to end against nginx 1.31.3 with ngx_cache_purge.

Install

From the admin: Plugins → Manage plugins → Browse, find nginx Cache, then Install. Or: php oc-cli.php market:install nginx-cache. Needs ShopClass 6.2.0 or later (tested up to 6.4).

What it is for

ShopClass sets Cache-Control: public, s-maxage=30 on public pages, and nginx honours it. Thirty seconds is short because nothing can invalidate an entry, so time is the only eviction. That means the origin re-renders every URL every thirty seconds for as long as anyone is looking at it.

This plugin lengthens the window and takes responsibility for correctness instead: when a listing changes, the pages showing it are purged immediately.

What it does not buy is page speed: with use_stale updating + background_update on, an expiring entry is already served stale in under a millisecond while it refreshes behind the request. The saving is origin renders: one per URL per hour instead of one per thirty seconds.

What it requires

nginx built with ngx_cache_purge. That module is not in the official image; it builds cleanly as a dynamic module against current nginx (verified on 1.31.3) in a two-stage Dockerfile, which is stock nginx plus one module rather than a switch to OpenResty. The plugin's Setup page prints the Dockerfile, the load_module line and the purge location block for this install.

The bundled ShopClass Docker image ships it preconfigured. A standalone install adds it by hand.

Safety

The longer window is gated on a self-test. Until the plugin has primed a URL, purged it, and confirmed the entry is gone, it serves core's own thirty seconds. A long window with a purge that quietly does not work is worse than no plugin at all. The failure is invisible and lasts an hour, so it is not something the plugin will do on trust. The flag clears whenever the endpoint or host changes.

What it purges, and what it does not

Purged, because their URLs can be named: the listing itself and one per locale, the homepage, the listing's category page, and the seller's public profile.

Not purged, and so left on core's short window:

  • search results carrying parameters: every keyword, filter, sort and page number is its own cache entry, the set cannot be enumerated, and a newly posted listing has to appear in them;
  • any URL with a query string, including ?comments-page=2 on a listing and ?utm_source=… on the home page. Each is a separate cache entry that no purge names;
  • every page, if permalinks are off, since the canonical URL of each is then a query URL itself.

The rule behind all three: a page is held longer only when the URL being served is the one a purge will name.

Site-wide changes purge everything. A theme switch, a plugin enabled or disabled, or a settings, maintenance, language, currency, widget, category or page change can touch every page. For those the plugin sends one PURGE /index.php to the origin of the purge endpoint, and the purge_all line from Setup clears the whole zone. If nginx refuses it, the plugin purges what it can name (home, every enabled category, static pages, the sitemap) and queues a purge-everything retry for the next cron run. Purge everything now on the settings page does the same by hand.

On the Shopclass Docker image 6.4.2+ with OSC_MICROCACHE=1, core purges everything itself through OSC_PAGE_CACHE_PURGE_URL. The plugin's own purge-all request is skipped, no fallback or queued retry is made, and the purge_all line is not needed. Purge everything now asks core, which clears the cache at the end of the request. Older cores and other installs work as described above. On Shopclass 6.4.3+ this also holds for cron and CLI commands in the image.

The purge_all line allows 127.0.0.1 only. Where PHP runs in another container, add that container's address to it by hand.

Narrow set_real_ip_from to your proxy. With real_ip reading X-Forwarded-For, nginx checks the allow list against the forwarded address. If set_real_ip_from trusts more than the proxy itself, an internet client can claim 127.0.0.1 or a private address and pass the allow list on both the purge location and the purge_all line.

Events it listens to

The set the Cloudflare plugin uses, which is the tested list of what core actually fires, plus one more:

posted_item, edited_item, after_delete_item, enable_item, disable_item, activate_item, deactivate_item, item_premium_on, item_premium_off, item_expiration_updated, add_category, after_delete_category, edit_page, after_delete_page, and invalidate_item_cache.

The last one (6.2.0+) fires when a storage offload moves a listing's images.

For purge-everything: page_cache_purge, which core 6.4.2+ fires once per request for every site-wide change. On older cores the plugin listens to theme_activate, after_plugin_activate, after_plugin_deactivate and admin_form_after_save instead. So on cores 6.2 to 6.4.1, any admin settings save purges the whole cache.

Relationship to the Cloudflare plugin

They compose. shopclass-plugin-cloudflare owns the edge; this owns the origin. Both hang off the same core hooks and share no code. Run either or both.

Configuration

Two pages under Plugins: the settings, and a Setup page that prints the nginx configuration this install needs (its own host, its own scheme, its own nginx version) rather than a sample to adapt. The same config is in nginx/shopclass-cache.conf for reading outside the admin.

Preference (nginx_cache section) Default
purge_endpoint $SHOPCLASS_PURGE_ENDPOINT, else <nginx's scheme>://127.0.0.1/purge
purge_host $SHOPCLASS_PURGE_HOST, else the site's host and port. One per line, see below
ttl_item, ttl_page, ttl_aggregate 3600, capped at 3600

Both halves of the endpoint matter and neither is obvious: the request must reach the origin and present the host and scheme the cache key was built with. Test purge proves that combination rather than trusting it, and changing either setting closes the gate again.

Test purge also proves purge-everything, and stores the result in purge_all_ok. That does not gate the longer windows; the settings page warns until it passes. Where core handles purge-everything (Docker image), the probe is skipped and the result is set to passed.

List every hostname the site answers on. nginx files a separate copy of each page under each Host it was asked with, so a name left out goes on serving what it already had for the whole window: www.example.com when the site is configured as example.com, an alias, a staging domain. Test purge primes and purges each one in turn, because priming with Host: X files an entry under X and can therefore prove it. The one thing it cannot prove is that those names are what visitors send, so it requires the site's own host to be among them: a list of typos would otherwise verify itself perfectly and purge nothing anybody reads.

Times are capped at 3600 seconds. Past that, the form token in a cached page can expire before a visitor submits the form.

Extending

Filter Purpose
nginx_cache_item_urls add URLs for a theme's own item routes
nginx_cache_purge_urls take the whole list and deliver it somewhere else

Licence

GPL-3.0-or-later. © Navjot Tomer (Mindstellar) and contributors.

About

nginx page cache plugin for ShopClass: keeps pages in nginx's FastCGI cache for an hour, and clears them the moment a listing changes.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages