Home / Blog / Redis Caching in Laravel: Practical Patterns to Speed Up Your App
Web Development

Redis Caching in Laravel: Practical Patterns to Speed Up Your App

AJAjish Stephen August 8, 2026 7 min read
Redis Caching in Laravel: Practical Patterns to Speed Up Your App

Adding a cache without a strategy usually just moves the problem — stale data shows up somewhere unexpected, or the cache gets so complicated that debugging it takes longer than the original slow query did. This walks through four patterns that actually earn their place in a Laravel app: query caching, cache tags for clean invalidation, rate limiting, and session storage.

Setup

Point Laravel at Redis in .env:

CACHE_STORE=redis
SESSION_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

Make sure the predis/predis package is installed, or set REDIS_CLIENT=phpredis if you're using the PHP extension instead.

Pattern 1: Query caching with automatic invalidation

Wrap an expensive query with Cache::remember(), and use cache tags so you can invalidate related entries as a group rather than tracking individual keys:

useIlluminate\Support\Facades\Cache;

$products= Cache::tags(['products'])->remember(
  "products.category.{$categoryId}",
  3600,
  function() use ($categoryId) {
    returnProduct::where('category_id',$categoryId)
      ->with('reviews')
      ->get();
  }
);

// Later, when a product changes — clear only the tagged entries
Cache::tags(['products'])->flush();

This means updating any product only needs one flush() call, instead of hunting down every cache key that might contain stale product data.

Pattern 2: Model observer invalidation

Tying cache flushes to a model observer means invalidation happens automatically, not something you have to remember to add every time you touch the model elsewhere in the codebase:

namespaceApp\Observers;

useApp\Models\Product;
useIlluminate\Support\Facades\Cache;

classProductObserver
{
  public functionsaved(Product$product)
  {
    Cache::tags(['products'])->flush();
  }

  public functiondeleted(Product$product)
  {
    Cache::tags(['products'])->flush();
  }
}

Register it in AppServiceProvider::boot() with Product::observe(ProductObserver::class); and every save or delete keeps the cache honest automatically.

Pattern 3: Rate limiting with Redis

Laravel's built-in throttle middleware already uses your configured cache store, so switching to Redis automatically makes rate limiting share state correctly across multiple app servers — something the file or array driver can't do:

Route::middleware('throttle:api')->group(function() {
  Route::get('/products', [ProductController::class, 'index']);
});

// Custom limits per route, e.g. 5 requests per minute for a sensitive endpoint
Route::middleware('throttle:5,1')->post('/password/reset', ...);

Pattern 4: Session storage across multiple servers

Once you're running more than one app server behind a load balancer, file-based sessions break — a user's session might land on a server that never saw their login. Redis session storage fixes this by centralizing session state:

# .env
SESSION_DRIVER=redis
SESSION_CONNECTION=default

No code changes needed beyond this — Laravel's session handling automatically routes through Redis once the driver is set, and every app server now reads from the same session store.

Where caching actually goes wrong

The failures I see most often aren't performance failures — they're correctness failures. A cache without a real invalidation trigger tied to the actual data change will eventually serve something stale to a user, and by the time someone notices, it's already eroded trust in the feature. Cache aggressively, but invalidate deliberately — every cache write should have a clear answer to "what event clears this."

Working on a Laravel app that's starting to slow down under real traffic? This is exactly the kind of performance work I do as part of web development engagements.

Common questions

Should I use Redis or Laravel's file cache driver?
File caching is fine for small, single-server apps with light caching needs. Redis is worth adopting once you're running multiple app servers, need cache tags for selective invalidation, or want sub-millisecond reads under real load.
How long should a cache entry live?
It depends entirely on how often the underlying data changes and how stale a result is acceptable. Start with a short TTL and increase it once you understand the actual staleness tolerance for that specific piece of data.
What's the difference between cache tags and plain keys?
A plain key is invalidated one at a time. Cache tags let you group related entries and flush them all at once, which is far more manageable when one change should invalidate many cached views of that data.
Can Redis caching introduce bugs if I'm not careful?
Yes — the most common issue is serving stale data after an update because the cache wasn't invalidated. Always pair a cache write with a clear invalidation strategy tied to the actual data change.
Is your app slowing down under real traffic?
I diagnose and fix performance bottlenecks in production Laravel applications.
Explore Web Development →
© Copyright 2024 Ajish Stephen