Short answer. A Laravel Redis cache is the fastest safe default for application caching in 2026. Set CACHE_STORE=redis, install the phpredis extension, give the cache its own Redis database, then wrap expensive reads in Cache::remember() with deliberate TTLs. Add atomic locks where traffic spikes and tags where invalidation is grouped.

Most Laravel apps do not have a caching problem. They have a caching plan problem. Redis gets installed, someone caches one dashboard query with a 60 second TTL, and 18 months later nobody can say which keys exist, what invalidates them, or why php artisan cache:clear sometimes takes queued jobs with it.

You already know Redis is faster than the file or database driver. The useful part is everything after that decision. This playbook covers the configuration that avoids footguns, the key and TTL conventions that survive a growing team, tags and locks for real traffic, what to cache first, and the Laravel caching mistakes that quietly undo the gains.

Key Takeaways

Why a Laravel Redis cache beats the default drivers

Laravel 11 moved the default cache store to database, and Laravel 10 and earlier defaulted to file. Both work. Neither is what you want under sustained load. The file driver writes serialized values to disk and shares nothing across app servers. The database driver pushes extra reads and writes onto the exact resource you are usually trying to protect.

Redis keeps values in memory, speaks a protocol built for this job, and supports the two features that keep caching maintainable as an app grows, tags and atomic locks. It also backs queues, sessions, rate limiting, and broadcasting, so a single dependency covers a lot of Laravel surface area. The Laravel cache documentation has the full driver list if you want the specifics.

Driver

Cache tags

Best fit

Watch out for

file

No

Local development, single small server

Disk I/O, no shared state across servers

database

No

Small apps with no extra infrastructure

Adds load to the database you are shielding

memcached

Yes

Plain key value caching at scale

No persistence, fewer data structures

redis

Yes

Cache, locks, rate limits, sessions, queues

Memory ceiling and eviction policy need attention

One note for 2026. Valkey is a protocol compatible fork of Redis, so the same Laravel redis driver and phpredis client work against it with no code changes. If your host offers Valkey rather than Redis, every pattern below still applies.

How to configure a Laravel Redis cache the right way

Three configuration decisions do most of the work. Get these right on day one and you avoid the incidents that make teams distrust their own cache.

Start with the environment values

# .env
CACHE_STORE=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_CACHE_DB=1
CACHE_PREFIX=acme_cache

On Laravel 10 and older the variable is CACHE_DRIVER rather than CACHE_STORE. Set CACHE_PREFIX explicitly when several apps or environments share one Redis instance, otherwise a staging deploy can read production keys.

Pick phpredis over predis

phpredis is a C extension, predis is pure PHP. For a cache that sits in the hot path of every request, the extension is the better default, and the Laravel Redis documentation covers the connection options it exposes. Two of those options are easy wins on large payloads.

// config/database.php
'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'prefix' => env('REDIS_PREFIX', 'acme_database_'),
        'serializer' => Redis::SERIALIZER_IGBINARY,
        'compression' => Redis::COMPRESSION_LZ4,
    ],
],

igbinary and LZ4 have to be compiled into your phpredis build, so check php --ri redis before you rely on them. Where they are available, cached collections and report payloads take noticeably less memory per key.

Build your Laravel app with AI

Skip the boilerplate. StackMivo turns a plain-English prompt into production-ready Laravel apps, with models, migrations, controllers, CRUD, auth, and tests.

Get started free

Give the cache its own Redis database

Laravel's Redis cache store implements flush() as a FLUSHDB call on the cache connection. That means php artisan cache:clear empties the whole database, not just your cache keys. The default Laravel config already separates them by pointing the cache connection at database 1, and keeping that separation is the difference between a harmless cache clear and a deploy that deletes pending jobs.

Managed Redis in cluster mode often exposes database 0 only. If that is your setup, run a second instance for queues and sessions instead of sharing one. This is the kind of Laravel specific detail that generic tooling misses and that Laravel-native intelligence is built to get right.

Set an eviction policy before you need one

redis-cli CONFIG SET maxmemory 2gb
redis-cli CONFIG SET maxmemory-policy allkeys-lru

The Redis default policy is noeviction, which rejects writes once memory is full rather than dropping old keys. For a cache only instance that is the wrong behaviour, and the Redis eviction reference explains each policy in detail. Persist the values in redis.conf so a restart does not lose them, and never run allkeys-lru on an instance that also holds queued jobs, because eviction does not know a job from a cached list.

Laravel cache best practices for keys and TTLs

Caching fails at the invalidation step far more often than the read step. A key convention fixes most of it. Use a predictable shape of scope, entity, qualifier, and version.

use Illuminate\Support\Facades\Cache;

public function topArticles(int $tenantId): Collection
{
    $key = "tenant:{$tenantId}:articles:top:v2";

    return Cache::remember($key, now()->addMinutes(15), function () use ($tenantId) {
        return Article::query()
            ->where('tenant_id', $tenantId)
            ->withCount('views')
            ->orderByDesc('views_count')
            ->limit(10)
            ->get();
    });
}

The tenant scope prevents cross account leaks. The v2 suffix lets you change the payload shape in a deploy without a manual flush, because old keys simply age out. Pick TTLs by how stale the data may safely be rather than by habit. Hot counters live for seconds, listings for minutes, reference data for hours, and heavy aggregates get cached until something explicitly invalidates them.

Laravel 11 and 12 also ship Cache::flexible(), which is the pattern you want for slow reports. It serves a fresh value inside the first window, keeps serving the stale value inside the second, and refreshes in the background after the response is sent.

$report = Cache::flexible("tenant:{$tenantId}:revenue:v1", [300, 3600], function () use ($tenantId) {
    return RevenueReport::for($tenantId)->compute();
});

No user waits on the rebuild, and no request ever hits a cold key inside the hour. That single change turns a slow dashboard into a fast one without touching the query.

Cache tags and atomic locks in a Laravel Redis cache

Tags let you invalidate a group of keys without tracking each name yourself.

Cache::tags(["tenant:{$tenantId}", 'articles'])->put($key, $payload, 900);

// after an article is published
Cache::tags(['articles'])->flush();

Be deliberate about tag size. Laravel tracks tagged keys in Redis and removes them on flush, so a tag holding hundreds of thousands of keys makes invalidation expensive. For very large groups a version counter is cheaper, because bumping one integer orphans every old key at once and lets eviction reclaim the memory.

$version = Cache::rememberForever("tenant:{$tenantId}:cache_version", fn () => 1);
$key = "tenant:{$tenantId}:articles:top:v{$version}";

// invalidate everything for this tenant
Cache::increment("tenant:{$tenantId}:cache_version");

Locks solve the other half of the problem. When a popular key expires, every concurrent request misses at the same moment and all of them run the expensive query. That stampede is how a cache makes an outage worse instead of better.

$lock = Cache::lock("report:{$id}:build", 30);

if ($lock->get()) {
    try {
        Cache::put("report:{$id}", $this->buildReport($id), 3600);
    } finally {
        $lock->release();
    }
}

Use Cache::lock('name', 10)->block(5, $callback) when the waiting requests should get the fresh value rather than a miss. One builder does the work, everyone else reads the result.

What to cache first in a Laravel application

Caching order matters more than caching volume. Work down this list.

  1. Nothing, yet. Fix N+1 queries with eager loading and add the missing indexes. Caching a bad query buys time, not performance.

  2. Aggregates and reports. Counts, revenue rollups, and leaderboards are slow, tolerant of staleness, and ideal for Cache::flexible().

  3. Reference data. Plans, categories, currencies, and feature flags change rarely and get read constantly.

  4. Third party responses. Every external API call you cache is one less timeout in your own request cycle.

  5. Rendered fragments. Expensive Blade or Livewire partials that do not vary per user.

A four person team running a Laravel analytics dashboard spent a sprint caching every widget on their main screen. Load times barely moved. The real cost was a single unindexed orderBy on a 12 million row events table that ran behind three of those widgets. They added the index, removed two thirds of the cache keys they had written, and the page got faster with less code to maintain. Curious how this looks when the caching layer is generated alongside the feature? Try StackMivo Now.

Keep config:cache, route:cache, and view:cache in a separate mental bucket. Those are file based build artifacts for deploys, not application data, and Redis has nothing to do with them.

Laravel Redis cache mistakes that quietly cost performance

These are the recurring ones, in rough order of how much damage they do.

  • Sharing one Redis database. A cache:clear during a deploy runs FLUSHDB and takes queued jobs and sessions with it.

  • Caching Eloquent models with loaded relations. You pay serialization cost on every write and read, and stale nested data is hard to spot. Cache arrays or small DTOs instead.

  • Keys without a scope. Missing tenant, locale, or role in the key is a data leak, not a performance bug.

  • rememberForever with no invalidation path. Forever means forever until someone flushes the whole store by hand at 2am.

  • No visibility. Run redis-cli INFO stats and watch keyspace_hits against keyspace_misses. A hit ratio nobody measures is a guess.

An agency inherited a multi-tenant Laravel CRM where the previous team had cached the tenant dashboard aggressively but keyed everything by user ID instead of tenant ID. Two accounts under the same parent company saw each other's pipeline totals for a week before anyone reported it. The fix was a one line key change plus a Pest test asserting that two tenants never resolve the same cache key. If you are working through similar inherited decisions, our guide to building a CRM with Laravel covers the structure that avoids them.

Shipping the caching layer without writing all the boilerplate

Every pattern here is mechanical once you have decided on it. Cache service classes, key builders, model observers that invalidate on save, lock wrappers, and the tests that prove invalidation works are all predictable Laravel code, and writing them by hand is where good caching plans go to die.

That is the gap AI code generation closes for Laravel teams. Describe the caching behaviour you want in plain English, including the TTL windows, the tenant scope, and the invalidation triggers, and StackMivo generates production-ready Laravel apps and features around it as standard code you own, read, and edit. It targets Laravel 9 through 12, so the output matches the conventions of the version you are actually running. When the feature is ready, one-click deployment pushes it to Laravel Cloud, Forge, Ploi, or your own SSH server with migrations run for you. You can get started free and try it on a single cached endpoint before committing to anything larger.

Where to take your Laravel Redis cache next

The playbook is short enough to act on this week. Point CACHE_STORE at Redis with the phpredis client, isolate the cache in its own Redis database, and set maxmemory with allkeys-lru so a full cache degrades instead of failing. Adopt one key convention that includes tenant and version, then choose TTLs by acceptable staleness rather than habit.

Reach for Cache::flexible() on slow reports and Cache::lock() on anything expensive enough to stampede. Before any of it, spend an hour on eager loading and indexes, because a Laravel Redis cache multiplies whatever is behind it, including the bad parts.

Measure the hit ratio, write a test for every invalidation rule, and keep the number of cache keys small enough that a new engineer can read the list and understand the system. Caching done this way stops being a source of mystery bugs and becomes what it should be, the cheapest performance you will ever ship.

Build your Laravel app with AI

Skip the boilerplate. StackMivo turns a plain-English prompt into production-ready Laravel apps, with models, migrations, controllers, CRUD, auth, and tests.

Get started free