Laravel Redis Integration — Complete Guide with Real-World Use Cases | Zeus Technocrats

Laravel Redis Integration — Complete Guide with Real-World Use Cases & Examples

Published: 17 March 2026 · Category: Laravel / Redis · Author: Zeus Technocrats

Redis is the fastest in-memory data store on the planet. This guide covers everything — installation, configuration, caching, sessions, queues, pub/sub, rate limiting, leaderboards, and real-time counters — all with production-ready Laravel code examples and explanations of how Redis works under the hood.

What is Redis?

Redis (Remote Dictionary Server) is an open-source, in-memory data structure store. Unlike a traditional database that reads from disk, Redis keeps everything in RAM — making reads and writes blazing fast (sub-millisecond latency).

Redis supports rich data structures: strings, hashes, lists, sets, sorted sets, bitmaps, streams, and more. Each one unlocks a different real-world pattern — sorted sets power leaderboards, pub/sub powers real-time messaging, lists power job queues.

Key facts: Redis processes over 1 million read operations per second on modest hardware. It is the most popular in-memory data store and the #1 recommended cache/queue backend for Laravel in production.

How Redis Works — Under the Hood

Redis runs as a single-threaded server process. All commands are executed sequentially in memory — no disk I/O on the hot path. Here is the request lifecycle for a Laravel cache read:

Redis data persistence

Despite being in-memory, Redis can persist data to disk using two strategies:

  • RDB (snapshots) — saves a point-in-time snapshot at configured intervals. Fast restores, small files.
  • AOF (Append Only File) — logs every write command. Slower but much more durable — can replay all operations on restart.

For Laravel cache and sessions, RDB is usually sufficient. For queues where job loss would be costly, enable AOF.

Redis data structures used by Laravel

StructureRedis TypeLaravel Feature
Key → ValueStringCache, Sessions, Atomic Locks
Named fieldsHashCache tags (internal)
Ordered queueListQueue driver (jobs pushed to list)
Unique membersSetQueue delayed jobs, deduplication
Scored rankingSorted SetLeaderboards, rate limiting windows
Message channelsPub/SubBroadcasting, real-time events

Installation & Setup

1. Install Redis server

# Ubuntu / Debian
sudo apt update && sudo apt install redis-server -y
sudo systemctl enable redis-server
sudo systemctl start redis-server

# Verify it is running
redis-cli ping
# Output: PONG

2. Install PHP Redis client

Laravel supports two PHP clients — PhpRedis (C extension, fastest) and Predis (pure PHP, no extension needed). Predis is the easiest to get started with:

# Option A — Predis (recommended for getting started)
composer require predis/predis

# Option B — PhpRedis extension (recommended for production performance)
sudo apt install php-redis
# Then set REDIS_CLIENT=phpredis in .env

3. Install Laravel Horizon (optional but recommended)

Horizon gives you a beautiful real-time dashboard for Redis queues:

composer require laravel/horizon
php artisan horizon:install
php artisan migrate

Laravel Configuration

.env settings

REDIS_CLIENT=predis          # or phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0

# Tell Laravel to use Redis for cache, sessions, and queues
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

config/database.php — Redis connection

'redis' => [
    'client' => env('REDIS_CLIENT', 'predis'),

    'default' => [
        'host'     => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port'     => env('REDIS_PORT', 6379),
        'database' => env('REDIS_DB', 0),
    ],

    // Separate DB for cache to avoid key collisions
    'cache' => [
        'host'     => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port'     => env('REDIS_PORT', 6379),
        'database' => env('REDIS_CACHE_DB', 1),
    ],
],
Key prefix tip: Always set REDIS_PREFIX in .env (e.g. myapp_) when multiple Laravel apps share the same Redis instance to avoid key collisions between apps.

Real-World Use Cases Overview

Caching

Cache DB queries, API responses, and computed data to reduce load and latency.

🔐

Sessions

Store user sessions in Redis for fast reads and horizontal scaling across servers.

📬

Queues & Jobs

Push background jobs to Redis lists — process emails, webhooks, reports asynchronously.

🚦

Rate Limiting

Throttle API endpoints per user or IP with atomic Redis counters and TTL windows.

📡

Pub / Sub

Broadcast real-time events to connected clients via Redis channels and Laravel Echo.

🏆

Leaderboards

Build real-time sorted leaderboards using Redis Sorted Sets with O(log N) inserts.

🔢

Counters

Atomic page view, like, and click counters that never have race conditions.

🔒

Atomic Locks

Prevent duplicate job execution or concurrent writes with distributed Redis locks.

1. Caching with Redis

Caching is the most common Redis use case. Instead of hitting your MySQL database on every request, store the result in Redis and serve it instantly from memory on subsequent calls.

Basic cache operations

use Illuminate\Support\Facades\Cache;

// Store a value for 60 minutes
Cache::put('user:42:profile', $profileData, now()->addMinutes(60));

// Retrieve — returns null if expired or missing
$profile = Cache::get('user:42:profile');

// Store forever (no expiry)
Cache::forever('site:settings', $settings);

// Delete a key
Cache::forget('user:42:profile');

// Check existence
if (Cache::has('user:42:profile')) { ... }

Remember pattern — the most useful cache helper

// Fetch from cache OR run the closure and store the result
$products = Cache::remember('homepage:products', now()->addHours(2), function () {
    return Product::with('category')
                  ->where('featured', true)
                  ->orderBy('sort_order')
                  ->get();
});

// rememberForever — no expiry
$countries = Cache::rememberForever('countries:all', fn () => Country::all());

Cache tags — group and flush related keys

// Store tagged cache entries
Cache::tags(['products', 'homepage'])->put('featured', $featured, 3600);
Cache::tags(['products', 'category:5'])->put('cat5_items', $items, 3600);

// Flush ALL product-related caches at once (e.g., after product update)
Cache::tags('products')->flush();
Real-world impact: Replacing a slow 400ms database query with a Redis cache hit reduces response time to under 2ms — a 200x improvement for your most visited pages.

Cache with model observers — auto-invalidate on update

// app/Observers/ProductObserver.php
class ProductObserver
{
    public function updated(Product $product): void
    {
        // Invalidate all product-related caches when any product changes
        Cache::tags('products')->flush();
        Cache::forget("product:{$product->id}");
    }

    public function deleted(Product $product): void
    {
        Cache::tags('products')->flush();
    }
}

// Register in AppServiceProvider::boot()
Product::observe(ProductObserver::class);

2. Sessions in Redis

By default Laravel stores sessions in files. Switching to Redis makes sessions faster and allows multiple application servers to share session state — essential for horizontal scaling.

Enable Redis sessions

# .env
SESSION_DRIVER=redis
SESSION_LIFETIME=120      # minutes
SESSION_CONNECTION=default # matches config/database.php redis connection name

No code changes are needed — Laravel handles everything transparently. Sessions are stored as Redis strings with automatic TTL expiry.

Working with sessions in controllers

// Store data in session
session(['cart_id' => $cart->id]);
request()->session()->put('last_viewed', $product->id);

// Retrieve
$cartId = session('cart_id');

// Flash — available only for the next request
session()->flash('success', 'Order placed successfully!');

// Forget
session()->forget('cart_id');
session()->flush(); // clear entire session
Under the hood: Each session is stored as a Redis key laravel_session:{session_id} with a TTL equal to your SESSION_LIFETIME. Redis automatically removes expired sessions — no garbage collection needed.

3. Queues & Background Jobs

Redis queues let you defer slow work — sending emails, generating PDFs, calling third-party APIs — to background workers so your HTTP response returns instantly.

How it works

When you dispatch() a job, Laravel pushes a serialised payload onto a Redis List. Queue workers continuously BLPOP (blocking left-pop) from the list and process jobs one by one.

Create and dispatch a job

php artisan make:job SendOrderConfirmation
// app/Jobs/SendOrderConfirmation.php
namespace App\Jobs;

use App\Models\Order;
use App\Mail\OrderConfirmed;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;

class SendOrderConfirmation implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    public int $tries = 3;          // retry up to 3 times on failure
    public int $backoff = 60;       // wait 60 seconds between retries

    public function __construct(public Order $order) {}

    public function handle(): void
    {
        Mail::to($this->order->user->email)
            ->send(new OrderConfirmed($this->order));
    }
}
// Dispatch from a controller — returns instantly to the user
SendOrderConfirmation::dispatch($order);

// Dispatch to a specific queue
SendOrderConfirmation::dispatch($order)->onQueue('emails');

// Delay by 10 minutes
SendOrderConfirmation::dispatch($order)->delay(now()->addMinutes(10));

Start queue workers

# Process the default queue
php artisan queue:work redis

# Process a specific named queue
php artisan queue:work redis --queue=emails

# Process with concurrency using Horizon
php artisan horizon
Production: Always run queue:work under Supervisor so workers restart automatically on crash. Never use queue:listen in production — it re-boots the framework on every job.

4. Rate Limiting with Redis

Redis is perfect for rate limiting because of its atomic INCR and EXPIRE commands. Laravel's built-in rate limiter uses Redis internally.

Define rate limiters in AppServiceProvider

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    // Global API limiter — 60 requests per minute per user/IP
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)
                    ->by($request->user()?->id ?: $request->ip());
    });

    // Strict OTP limiter — 5 requests per 10 minutes per IP
    RateLimiter::for('otp', function (Request $request) {
        return Limit::perMinutes(10, 5)
                    ->by($request->ip())
                    ->response(function () {
                        return response()->json([
                            'message' => 'Too many OTP attempts. Try again later.'
                        ], 429);
                    });
    });

    // Per-second burst limiter (Laravel 11+)
    RateLimiter::for('uploads', function (Request $request) {
        return [
            Limit::perSecond(2)->by($request->user()->id),
            Limit::perMinute(30)->by($request->user()->id),
        ];
    });
}

Apply to routes

// routes/api.php
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::post('/orders', OrderController::class);
});

Route::middleware('throttle:otp')->post('/auth/otp', OtpController::class);

Manual rate limiting in controllers

use Illuminate\Support\Facades\RateLimiter;

$key = 'login-attempts:' . request()->ip();

if (RateLimiter::tooManyAttempts($key, 5)) {
    $seconds = RateLimiter::availableIn($key);
    return response()->json([
        'message' => "Too many attempts. Retry in {$seconds} seconds."
    ], 429);
}

RateLimiter::hit($key, 300); // increment, expires in 300s

// ... authenticate user

RateLimiter::clear($key); // reset on successful login

5. Pub / Sub — Real-time Events

Redis Pub/Sub lets one part of your application publish messages to a channel and any number of subscribers receive them instantly. Laravel uses this under the hood for its redis broadcast driver.

Broadcasting setup

# .env
BROADCAST_DRIVER=redis

# Install Laravel Echo and Pusher JS (acts as Redis bridge via Soketi or Laravel Reverb)
npm install laravel-echo pusher-js

Define a broadcastable event

php artisan make:event OrderShipped
// app/Events/OrderShipped.php
namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;

class OrderShipped implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets;

    public function __construct(public Order $order) {}

    // Broadcast on a private channel for the order's user
    public function broadcastOn(): Channel
    {
        return new \Illuminate\Broadcasting\PrivateChannel(
            'orders.' . $this->order->user_id
        );
    }

    // Control exactly what data goes to the frontend
    public function broadcastWith(): array
    {
        return [
            'order_id' => $this->order->id,
            'status'   => $this->order->status,
            'message'  => 'Your order has been shipped!',
        ];
    }
}

Fire the event from a controller or job

// When order status changes
$order->update(['status' => 'shipped']);
broadcast(new OrderShipped($order))->toOthers();

Listen in JavaScript with Laravel Echo

// resources/js/app.js
window.Echo.private(`orders.${userId}`)
    .listen('OrderShipped', (event) => {
        console.log('Order shipped!', event.order_id);
        showNotification(event.message);
    });

Direct Redis Pub/Sub (without broadcasting)

use Illuminate\Support\Facades\Redis;

// Publisher — send a message to a channel
Redis::publish('notifications', json_encode([
    'user_id' => 42,
    'message' => 'You have a new follower!',
]));

// Subscriber — run this in a long-running artisan command
Redis::subscribe(['notifications'], function (string $message) {
    $data = json_decode($message, true);
    // Process notification...
    echo "Received: {$data['message']}\n";
});

6. Real-time Leaderboards with Sorted Sets

Redis Sorted Sets (ZADD, ZRANGE, ZRANK) are purpose-built for leaderboards. Each member has a floating-point score. Redis keeps them sorted automatically on every insert in O(log N) time.

Building a game leaderboard

use Illuminate\Support\Facades\Redis;

class LeaderboardService
{
    protected string $key = 'leaderboard:global';

    // Add or update a player's score
    public function addScore(int $userId, float $score): void
    {
        // ZADD key score member — updates if member already exists
        Redis::zadd($this->key, $score, "user:{$userId}");
    }

    // Increment score (e.g., +10 points per kill)
    public function incrementScore(int $userId, float $points): void
    {
        Redis::zincrby($this->key, $points, "user:{$userId}");
    }

    // Get top N players (highest score first)
    public function getTopPlayers(int $limit = 10): array
    {
        // ZREVRANGE returns highest-score members first
        // WITHSCORES includes the score alongside the member
        return Redis::zrevrange($this->key, 0, $limit - 1, 'WITHSCORES');
    }

    // Get a specific player's rank (0-indexed, lower = better)
    public function getRank(int $userId): int|null
    {
        $rank = Redis::zrevrank($this->key, "user:{$userId}");
        return $rank !== null ? $rank + 1 : null; // convert to 1-indexed
    }

    // Get player's score
    public function getScore(int $userId): float
    {
        return (float) Redis::zscore($this->key, "user:{$userId}");
    }
}

Usage in a controller

$lb = app(LeaderboardService::class);

// Player scores 50 points
$lb->incrementScore(auth()->id(), 50);

// Get top 10
$top10 = $lb->getTopPlayers(10);

// Get current player's rank
$myRank = $lb->getRank(auth()->id());
Why Redis over MySQL for leaderboards: A MySQL SELECT … ORDER BY score DESC LIMIT 10 on a 10 million row table requires a full index scan. Redis ZREVRANGE on the same data is O(log N + M) — the top 10 is returned in microseconds regardless of total player count.

7. Atomic Real-time Counters

Redis INCR is atomic — even with thousands of concurrent requests, it increments exactly once per call with zero race conditions. No transactions, no locking.

Page view counter

use Illuminate\Support\Facades\Redis;

class ViewCounter
{
    // Increment view count for a post
    public function increment(int $postId): int
    {
        return Redis::incr("post:{$postId}:views");
    }

    // Get current count
    public function get(int $postId): int
    {
        return (int) Redis::get("post:{$postId}:views") ?? 0;
    }

    // Flush to database periodically (e.g., every hour via scheduled job)
    public function syncToDatabase(int $postId): void
    {
        $views = $this->get($postId);

        Post::where('id', $postId)->increment('view_count', $views);

        // Reset Redis counter after syncing
        Redis::set("post:{$postId}:views", 0);
    }
}

Like / reaction counter with deduplication

// Use a Redis Set to track who liked — prevents duplicate likes
public function like(int $postId, int $userId): bool
{
    $key = "post:{$postId}:likes";

    // SADD returns 1 if added (new like), 0 if already existed (duplicate)
    $added = Redis::sadd($key, $userId);

    if ($added) {
        Redis::incr("post:{$postId}:like_count");
    }

    return (bool) $added;
}

public function unlike(int $postId, int $userId): void
{
    Redis::srem("post:{$postId}:likes", $userId);
    Redis::decr("post:{$postId}:like_count");
}

public function getLikeCount(int $postId): int
{
    return (int) Redis::get("post:{$postId}:like_count");
}

public function hasLiked(int $postId, int $userId): bool
{
    return (bool) Redis::sismember("post:{$postId}:likes", $userId);
}

8. Atomic Locks — Prevent Duplicate Execution

Redis locks let you ensure that a critical section of code (like processing a payment or sending an email) only runs once, even if multiple servers or workers try to execute it simultaneously.

Laravel Cache lock (recommended)

use Illuminate\Support\Facades\Cache;

// Attempt to acquire a lock for 10 seconds
$lock = Cache::lock('process-order:' . $order->id, 10);

if ($lock->get()) {
    try {
        // Only ONE server/worker executes this block at a time
        $this->processPayment($order);
        $this->sendConfirmation($order);
    } finally {
        $lock->release(); // always release, even on exception
    }
} else {
    // Another process is already handling this order
    throw new \RuntimeException('Order is already being processed.');
}

Block until lock is available

// Wait up to 5 seconds to acquire the lock, then execute
Cache::lock('generate-report', 30)->block(5, function () {
    // Generate report — guaranteed to run exclusively
    ReportService::generate();
});

Owner-token locks (for queue workers)

// Acquire lock and get owner token
$lock = Cache::lock('send-invoice:' . $invoiceId, 60);
$token = $lock->acquire();

// Pass token to a job that will release it
dispatch(new ProcessInvoice($invoiceId, $token));

// Inside the job — release using the token
Cache::restoreLock('send-invoice:' . $this->invoiceId, $this->token)->release();

Redis CLI Commands Cheatsheet

Useful commands for debugging Redis in your Laravel application:

# Connect to Redis CLI
redis-cli

# List all keys matching a pattern
KEYS laravel_cache:*

# Check a cached value
GET laravel_cache:homepage_products

# Check TTL remaining (in seconds, -1 = no expiry, -2 = key gone)
TTL laravel_cache:homepage_products

# Delete a specific key
DEL laravel_cache:homepage_products

# Check memory usage of a key
MEMORY USAGE laravel_cache:homepage_products

# Monitor all commands in real-time (debug only!)
MONITOR

# Get Redis server info and stats
INFO

# See all connected clients
CLIENT LIST

# Flush entire database (DANGER — removes everything)
FLUSHDB
Warning: Never run KEYS * or FLUSHDB on a production Redis server. KEYS blocks the server while scanning and FLUSHDB wipes all data. Use SCAN for safe key iteration in production.

Production Checklist

  • Security Always set a strong requirepass in redis.conf and restrict Redis to localhost or a private network — never expose port 6379 publicly.
  • Persistence Enable AOF persistence for queues (appendonly yes in redis.conf) so jobs survive Redis restarts without data loss.
  • Prefix Set REDIS_PREFIX in .env when multiple apps share the same Redis instance to prevent key collisions.
  • Separate DBs Use different Redis databases (database: 0, 1, 2) for cache, sessions, and queues — makes flushing cache safe without affecting sessions.
  • Horizon Run Laravel Horizon for queue monitoring — it shows throughput, wait times, failed jobs, and worker health in a beautiful dashboard.
  • Memory limit Set maxmemory and maxmemory-policy allkeys-lru in redis.conf so Redis evicts least-recently-used cache keys instead of crashing when memory is full.
  • Supervisor Run queue workers under Supervisor in production so they restart automatically after crashes or deploys.
  • No KEYS in prod Use SCAN instead of KEYS * for key iteration — KEYS blocks all other operations during execution.
  • Monitoring Track Redis memory usage, hit/miss ratio, and connected clients via Laravel Horizon, Redis INFO, or tools like RedisInsight.

Conclusion

Redis transforms Laravel applications. What starts as a simple cache layer quickly becomes the backbone of your entire application — handling sessions, job queues, real-time pub/sub, rate limiting, leaderboards, and atomic counters, all from a single blazing-fast in-memory store.

The key to using Redis well is choosing the right data structure for each use case: strings for cache and counters, lists for queues, sorted sets for leaderboards, sets for deduplication, and pub/sub for real-time events. Laravel's Redis facade and cache abstractions make all of these patterns clean and testable.

Start with caching your slowest queries, switch sessions to Redis, and add queue workers — those three changes alone will make your application dramatically faster and more scalable. Build from there.

If you need help architecting or implementing a Redis-backed Laravel application — including Horizon setup, Redis Cluster for high availability, or advanced caching strategies — Contact Zeus Technocrats.