The Short Answer
Symfony's HttpCache has no built-in mechanism to broadcast invalidation events across nodes. A PURGE or BAN request is handled only by the node that receives it, because the default FileCacheStore writes to that node's local filesystem. To get cluster-wide consistency you must choose one of four architectural patterns:
- Shared cache backend — replace
FileCacheStore with a network store (Redis or Memcached) that every node reads and writes. A purge on any node deletes the key for all nodes.
- Message-bus propagation — the application publishes an invalidation event (e.g. via Symfony Messenger); a handler on each node calls
purge() on its local cache instance.
- Custom store with pub/sub — implement
StoreInterface backed by Redis Pub/Sub or similar, so one purge message triggers eviction on every subscriber.
- External reverse proxy — put Varnish or a CDN in front and forward
BAN requests to it; the proxy's own clustering handles distribution. Symfony only generates the ban, it does not distribute it.
Likely Explanation vs. Confirmed Behavior
Confirmed: HttpCache delegates storage to a StoreInterface implementation, and the bundled store is filesystem-based and node-local. There is no documented broadcast API in the component itself.
Likely, but verify against your version: the exact service wiring for swapping the store differs between the classic HttpCache kernel wrapper and FrameworkBundle's framework.http_cache integration. On recent Symfony versions (5.4/6.x), enabling http_cache: true uses the built-in store; to override it you decorate or replace the store service. Check your installed version's FrameworkExtension wiring before copying config from a blog post.
Steps for the Shared Backend Approach
- Point every node at the same Redis instance and use an identical key prefix. A minimal service definition looks like:
# config/services.yaml
services:
app.http_cache.store:
class: Symfony\Component\HttpKernel\HttpCache\Store
# or a custom StoreInterface implementation wrapping Redis
Symfony\Component\HttpKernel\HttpCache\HttpCache:
arguments:
$store: '@app.http_cache.store'
Note: the stock Store class is filesystem-only, so a Redis-backed setup requires a custom StoreInterface implementation — this is the part most tutorials gloss over.
- Send a
PURGE on one node, then request the same URL from a second node and confirm the response is a miss (check the X-Symfony-Cache debug header in dev, or your store's key listing).
- For Redis, verify directly:
redis-cli --scan --pattern '*http_cache*' | wc -l before and after the purge should drop identically regardless of which node received the request.
Message-Bus Alternative
If you keep local stores for latency reasons, dispatch an event and consume it on every node:
$bus->dispatch(new PurgeUrlMessage($url));
// handler on each node
public function __invoke(PurgeUrlMessage $msg): void
{
$this->httpCache->invalidate(Request::create($msg->url));
}
Make the handler idempotent — message ordering is not guaranteed, and a purge arriving before a late write can re-stale the entry.
Caveats
- Shared backends add network latency to every cache lookup; a slow purge can still race with concurrent reads.
- Sharing
FileCacheStore over NFS causes file-locking contention under load — avoid it.
One Diagnostic Question
Which store is currently configured for your HttpCache? If you are already on Redis, you only need to confirm the key prefix matches across nodes; if you are on the filesystem default, the decision is between swapping the store and adding Messenger-based propagation.