log in
consulting hosting industries the daily tools about contact

Redis Does Three Jobs on One Server and I've Never Replaced It

Five years of using Redis as queue, cache, and session store on a single box. Here's why I haven't touched it.

I keep waiting to find a reason to replace Redis with something more interesting. Five years in, across a dozen production deployments, I haven't found one.

That's not a ringing endorsement — it's more like the quiet satisfaction of a tool that just refuses to cause problems.

What Redis Actually Solves For Me

The pitch is usually "blazing fast in-memory data store," which is technically accurate and completely useless. Here's what it actually does in my stack:

Sessions — I run Redis as the session backend for every Laravel app I ship. PHP's default file-based sessions fall apart the moment you have more than one web server, and the database session driver adds a write on every request. Redis hits neither of those problems.

Cache — Full-page cache, query result cache, computed values I don't want to recalculate on every request. If a biotech client's dashboard aggregates 90 days of assay data from a LIMS API, that result is going into Redis for five minutes. The LIMS doesn't need to hear from me on every page load.

Queues — Email sends, PDF generation, webhook dispatch, third-party API calls that don't need to block the HTTP response. Laravel's queue system treats Redis as a first-class backend, and it works well.

Three jobs, one process, one server, one apt install redis-server. That's the whole pitch.

The Setup I Actually Use

I run Redis on the same box as PHP-FPM and Nginx for most clients. Medium-traffic web apps, nothing serving millions of requests a day. For that use case, a separate Redis server is almost always premature.

Here's config/database.php with the Redis connection I start from:

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

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

    '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),
    ],

    'queue' => [
        'host'     => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port'     => env('REDIS_PORT', 6379),
        'database' => env('REDIS_QUEUE_DB', 2),
    ],
],

Separate logical databases (0, 1, 2) for sessions, cache, and queues respectively. This is not strictly necessary — you can namespace keys instead — but I like being able to flush the cache database without torching active sessions or losing queued jobs. redis-cli -n 1 FLUSHDB is a lot more satisfying than trying to pattern-delete keys.

Then .env:

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=your_strong_password_here
REDIS_PORT=6379
REDIS_CACHE_DB=1
REDIS_QUEUE_DB=2

CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_CONNECTION=default

And the queue worker in Supervisor, which I configure on every server that processes jobs:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/myapp/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/myapp/storage/logs/worker.log
stopwaitsecs=3600

Two worker processes is usually enough for a mid-sized client app. I've bumped it to four on a print management platform that queues a lot of PDF render jobs, but two handles most things comfortably.

The Gotchas That Will Bite You

Memory limits with no eviction policy set. Out of the box, Redis will happily consume memory until the server falls over. Set maxmemory and maxmemory-policy in /etc/redis/redis.conf:

maxmemory 512mb
maxmemory-policy allkeys-lru

allkeys-lru evicts the least-recently-used keys when you hit the ceiling. For a cache, that's exactly what you want. For a queue, it's a disaster — evicted jobs are gone. This is why I keep queues on a separate logical database, and honestly for anything where job durability matters I'll set a separate Redis instance with noeviction and a much higher memory ceiling, or use the database queue driver instead. Losing a queued job silently is a bad day.

phpredis vs. predis. Use phpredis. It's a C extension, it's faster, and Laravel has supported it as the default client since version 9. Predis is a pure PHP fallback and it's noticeably slower under load. If you're on a new server and phpredis isn't installed: sudo apt install php-redis && sudo systemctl restart php8.x-fpm. Done.

The REDIS_DB confusion in older Laravel apps. I've inherited codebases where every connection pointed at database 0 and a CACHE_FLUSH in production would kill user sessions mid-request. Check your database.php. If you see 'database' => env('REDIS_DB', 0) on every connection, fix it before it bites someone.

Persistence is off by default. Redis doesn't write to disk unless you configure RDB snapshots or AOF logging. If the server restarts, your cache is gone (fine), your sessions are gone (annoying but recoverable), and your queued jobs are gone (bad). I enable RDB snapshots for queue instances:

save 900 1
save 300 10
save 60 10000

For anything where I genuinely cannot lose jobs — payment webhooks, order confirmation emails — I switch the queue driver to database and let MySQL handle durability. Redis queues are fast and convenient, but a database queue with a migration and a cron is a lot easier to reason about at 2am.

Keyspace collisions between apps. If you run two Laravel apps pointing at the same Redis instance, set a unique prefix per app. In config/cache.php:

'prefix' => env('CACHE_PREFIX', 'myapp_v1'),

I've seen two staging apps sharing a server clobber each other's sessions because nobody thought to set this. It's the kind of bug that looks like intermittent auth weirdness and takes way too long to track down.

When I'd Reach for This Setup

Almost always, honestly. A Laravel app on a single server or a small cluster, traffic under a few hundred concurrent users, a team that doesn't want to operate Kafka — Redis is the right call. It's in every major cloud provider's managed offering (ElastiCache, Upstash, Redis Cloud), the Laravel integration is mature, and the operational surface is small.

I integrated this pattern for a real estate client running an MLS data sync — heavy inbound webhook volume, a lot of background processing, user-facing cache of listing data. Single Redis instance, three logical databases, two queue workers. It's been running without incident for two years. I look at it approximately never.

Same story for a healthcare client's patient portal. Sessions are in Redis, API response cache is in Redis, appointment reminder jobs are queued through Redis. HIPAA-compliant hosting, Redis on the same private subnet as the app server, TLS everywhere. No drama.

When I Wouldn't

If you need guaranteed at-least-once delivery for financial transactions, I'd lean on the database queue driver or a proper message broker. If you're running a distributed system with multiple teams consuming from the same queues and you need consumer groups and replay, look at Kafka or SQS. If your cache working set is measured in gigabytes and you need fine-grained eviction control per data type, maybe Memcached makes sense, though I haven't touched Memcached in years.

And if someone on your team is proposing adding Redis Streams, Redis Pub/Sub, Redis Search, and Redis TimeSeries to replace five other systems because "Redis can do everything" — push back. Redis doing three boring jobs well is a feature. Redis as a distributed platform is a different bet.

The Bottom Line

Boring tech stays boring because it keeps working. Redis has been the most reliable thing in my stack for five years, and the most interesting thing I've ever had to do to it is bump a memory limit. That's the highest compliment I know how to give a piece of infrastructure.

Need help shipping something like this? Get in touch.