A busy Laravel app lives or dies by its cache. This guide covers the Redis-backed patterns that keep it fast under load, and what to use instead where Redis is not available.
Laravel's cache layer over Redis gives you tags (invalidate related keys together), atomic locks (stop two workers doing the same expensive job) and stale-while-revalidate caching with Cache::flexible(). This guide covers the patterns that matter in production. Redis is not available on Domain India shared hosting or on the App Platform, so run these patterns on your own VPS, or use Laravel's database cache driver where Redis isn't available.
The cache layer choice
| Driver | Speed | Tags support | Best for |
|---|---|---|---|
file | Slow, disk I/O | No | Small single-server sites, development |
database | Medium | No | Small Laravel apps where Redis isn't available |
memcached | Fast | Yes | Existing setups; Redis is the better choice for new apps |
redis | Fast | Yes | Production Laravel on a server you control |
Use Redis when you can run it. Where your Laravel app runs on Domain India decides what you can use:
- Shared hosting (cPanel, DirectAdmin, Webuzo): no Redis or Memcached server. Use the
databaseorfilecache driver; tags and Redis locks are not available there. Composer can't run on shared hosting, so buildvendor/locally and upload it. - App Platform: there is no Redis add-on. Each plan includes a PostgreSQL database, so use Laravel's
databasecache driver on it. Only Node.js is detected automatically, so a Laravel app needs your own Dockerfile. - VPS: install Redis (or Valkey) yourself from your OS packages. A VPS is self-managed, so you install, secure and update it.
Redis is not offered on any shared hosting plan and is not an App Platform add-on. If your app needs Redis for cache, sessions, locks or queues, run it on your own VPS and keep it bound to 127.0.0.1.
Step 1: Configure Laravel for Redis
.env (Laravel 11 and later use CACHE_STORE; older versions use CACHE_DRIVER):
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=your-strong-password
REDIS_DB=0
REDIS_CACHE_DB=1 # separate DB for cache vs sessionconfig/database.php: check the Redis client and connections:
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
],Install the phpredis extension on your VPS. On Ubuntu or Debian the package is simplest; PECL works on any Linux:
# Ubuntu / Debian
sudo apt install php-redis
# Any distribution, via PECL
sudo pecl install redis
echo "extension=redis.so" | sudo tee /etc/php.d/50-redis.ini
php -m | grep -i redisIf you can't install an extension, set REDIS_CLIENT=predis and add the predis/predis package to your project.
Step 2: Basic caching patterns
Remember (cache-aside):
$products = Cache::remember('products.active', 300, function () {
return Product::active()->with('category')->get();
});Invalidate on write:
Product::create($data);
Cache::forget('products.active');Forever (until manually cleared):
Cache::rememberForever('settings.all', fn() => Setting::all());Step 3: Cache tags (related-key invalidation)
The Redis driver supports tags. Tag related entries, then invalidate them all at once.
// On write:
Cache::tags(['products', 'category:5'])->put("product.$id", $product, 300);
// Elsewhere:
Cache::tags(['category:5'])->put('category.summary.5', $summary, 300);
// When something in category 5 changes:
Cache::tags(['category:5'])->flush();
// → both product and summary entries clearedTags don't work with the file, database or DynamoDB drivers. Your production and development environments must use the same driver, or tag code that works locally will fail in production.
Step 4: Atomic locks (prevent race conditions)
Two concurrent requests try to generate the same expensive report. Without a lock, both compute it and the work is wasted.
$report = Cache::get('expensive.report');
if ($report === null) {
$report = Cache::lock('report.lock', 10)->block(5, function () {
// Another process may have filled the cache while we waited for the lock
return Cache::remember('expensive.report', 3600, fn () => generateExpensiveReport());
});
}block(5, …) waits up to 5 seconds for the lock and throws LockTimeoutException if it can't get it. The lock itself expires after 10 seconds, so a crashed worker can't hold it forever.
Step 5: Cache stampede prevention
The classic problem: a popular key expires at 10:00:00 and 1,000 concurrent requests hit the database at once.
Fix 1: Atomic lock (above). Only one request regenerates; the others wait briefly and then read the fresh value.
Fix 2: Stale-while-revalidate with Cache::flexible() (Laravel 11.23 and later):
// Fresh for 5 minutes. Between 5 and 10 minutes old, the stale value is served
// and refreshed in the background after the response is sent.
$stats = Cache::flexible('dashboard.stats', [300, 600], function () {
return computeDashboardStats();
});This is the soft-TTL/hard-TTL pattern built in: users almost never wait for a recompute, and only one refresh runs at a time.
Fix 3: Probabilistic early expiration. Each request has a small, growing chance of refreshing the value shortly before it expires, so refreshes spread out instead of all landing at the expiry second. Store the expiry time and the recompute cost next to the value:
function rememberEarly(string $key, int $ttl, callable $callback, float $beta = 1.0)
{
$entry = Cache::get($key); // ['value' => ..., 'expires' => ..., 'delta' => ...]
$rand = mt_rand(1, mt_getrandmax()) / mt_getrandmax();
if ($entry && microtime(true) - $entry['delta'] * $beta * log($rand) < $entry['expires']) {
return $entry['value'];
}
$start = microtime(true);
$value = $callback();
$delta = microtime(true) - $start;
Cache::put($key, [
'value' => $value,
'expires' => microtime(true) + $ttl,
'delta' => $delta,
], $ttl);
return $value;
}For most apps, Cache::flexible() is the simpler choice.
Step 6: Full-page cache (middleware)
For guest-accessible pages:
// app/Http/Middleware/PageCache.php
public function handle($request, Closure $next)
{
if ($request->user() || $request->method() !== 'GET') {
return $next($request);
}
$key = 'page.' . sha1($request->fullUrl());
$cached = Cache::tags(['page-cache'])->get($key);
if ($cached) {
return response($cached['body'], $cached['status'])
->withHeaders($cached['headers'])
->header('X-Cache', 'HIT');
}
$response = $next($request);
if ($response->isSuccessful()) {
Cache::tags(['page-cache'])->put($key, [
'body' => $response->getContent(),
'status' => $response->getStatusCode(),
'headers' => array_filter($response->headers->all(), fn($k) =>
!in_array($k, ['set-cookie', 'date']), ARRAY_FILTER_USE_KEY),
], 300);
}
return $response->header('X-Cache', 'MISS');
}Invalidate all page cache on content edit:
Cache::tags(['page-cache'])->flush();Step 7: Query caching
$users = Cache::remember('users.active', 120, function () {
return User::where('is_active', true)->get();
});Cache the queries you have measured as slow, and invalidate them on write, rather than caching every query automatically.
Session in Redis
Instead of files (the default in older versions) or the database, store sessions in Redis. It is faster and works across several app servers.
SESSION_DRIVER=redis
SESSION_CONNECTION=defaultBenefits: no sticky sessions needed, and you can wipe all sessions at once after a security incident.
Monitoring cache health
Redis INFO:
redis-cli INFO stats
# keyspace_hits, keyspace_misses → compute hit rateLaravel Pulse has a cache card that shows hits and misses per key in production.
Laravel Telescope (development only) shows the cache operations of each request.
Laravel Horizon is a dashboard for Redis queues, not for the cache.
Rough targets: a hit rate above 80% for query cache and above 50% for page cache.
Common pitfalls
user.42.dashboard.2026-04-24T10:15:42 makes a new key every second. Round timestamps to the minute or hour.REDIS_PREFIX must differ.Cache::put('user', $user) serialises loaded relations too. Cache ->toArray() or a small DTO.Cache::rememberForever() a bad value never heals itself. Prefer an explicit long TTL (24 hours, 7 days).FAQ
Can I use Redis with Laravel on Domain India shared hosting?
No. Redis and Memcached are not available on Domain India shared hosting (cPanel, DirectAdmin or Webuzo), and the App Platform has no Redis add-on. Use Laravel's database or file cache driver there, or run Redis yourself on a Domain India VPS.
Redis or Valkey?
Valkey is a Linux Foundation fork of Redis and is compatible with the Redis protocol, so Laravel's redis driver works with it unchanged. Use whichever your operating system ships; AlmaLinux 10 ships Valkey.
How much RAM does Redis need?
It depends on how many keys you store and how big the values are. Start small, check redis-cli INFO memory under real traffic, and set maxmemory with maxmemory-policy allkeys-lru so Redis evicts old keys instead of running out of memory.
Does php artisan cache:clear clear tagged entries?
Yes. It clears the whole cache store, including tagged entries. For a targeted clear, use Cache::tags(['tag1'])->flush().
Should I cache in the controller, the model or the query layer?
Cache at the layer closest to the expensive operation, which is usually the repository or query layer. Controller-level caching is too coarse, because different users can end up sharing one entry, and caching inside model observers is easy to forget about.
Can I share one Redis between several Laravel apps?
Yes, but give each app a different REDIS_PREFIX or different REDIS_DB numbers. Sharing cache between apps couples them, so keep it separate where you can.
Ready to run Redis-powered Laravel? Choose a Domain India VPS and install Redis yourself, or read Server-side caching for what works on shared hosting.
A VPS gives you root access to install Redis or Valkey and tune it for your Laravel app.
View VPS plans