Direct answer
Lumen does not guarantee TTL enforcement as active file deletion across 9.x or 10.x. The file cache driver enforces expiration lazily: when Cache::get() is called, the store checks the stored expiration timestamp and treats an expired entry as a miss. The underlying file may remain on disk until it is read, forgotten, or flushed. If Cache::get() itself returns the old value after the TTL has passed, the cause is almost always a configuration mismatch, clock skew, or a different cache layer, not a failure of the file driver's TTL logic.
Confirmed behavior vs. likely explanation
- Confirmed: Lumen delegates file caching to the Illuminate
FileStore. Expiration is evaluated on read, not by a background purge. The file driver is not taggable, so Cache::tags() is unavailable on it. - Likely explanation: Persistent stale reads usually mean the serving process is resolving a different cache path, prefix, or store than the writer, or system clocks differ between nodes. Disk files surviving past TTL is expected lazy behavior, not a bug.
Invalidating a single key when data changes
- Use
Cache::forget('key') or Cache::store('file')->forget('key') in the same request or transaction that mutates the underlying data. - Repopulate immediately with
Cache::put('key', $value, $ttl) so the next read sees fresh data. - Reserve
Cache::flush() for cases where losing the entire store is acceptable; it is not a targeted invalidation tool.
Version-aware approach and concurrency
Keep the illuminate/cache component aligned with your Lumen line (Lumen 9 with Laravel 9 components, Lumen 10 with Laravel 10 components) and verify the installed version in composer.lock rather than relying on the Lumen major version label alone. Ensure cache configuration, prefix, and storage path are identical across every instance. For strict freshness under concurrent requests, prefer a shared cache such as Redis. If files must be used, place the cache directory on a shared volume with reliable locking and use short TTLs. Note that file locking is write-side only; it serializes writers but does not extend TTL or protect readers who have already loaded a stale value.
Practical verification
- In a scratch app, run
Cache::store('file')->put('ttl_test', 'v', 1), read it immediately, wait past the TTL, and read again; expect a miss. - Inspect the configured cache path before and after to confirm the file may still exist until read or forgotten.
- Boot a script through the app to print the resolved store class and whether it implements the taggable interface.
One diagnostic detail that changes the recommendation
Does Cache::get() return the stale value, or does the file merely still exist on disk? If get() returns it, capture the resolved cache driver, prefix, store, path, and exact key on the serving instance before changing any TTL logic. If only the file exists, lazy expiration is working as designed and no code change is needed.