Deployment
Redis & caching

Set Up Redis Caching for Django in Production

Configure Django's Redis cache for production: memory limits, eviction, authentication, key prefixes, timeouts, cached sessions, and checks.

Problem statement

Django's default cache is LocMemCache, which lives inside each process. In production with several Gunicorn workers or servers, every process has its own separate cache, so cached data is duplicated, invalidation only affects one worker, and features that rely on a shared cache (rate limiting, cached sessions, throttling) behave inconsistently.

Redis is the usual shared cache for Django, and Django has shipped a built-in Redis backend since 4.0. Pointing LOCATION at Redis is only part of the job. A production setup also needs to decide:

  • how much memory Redis may use and what it evicts when full
  • whether cache and Celery broker data share one Redis, and how to keep them apart
  • how Django authenticates to Redis and whether traffic is encrypted
  • what happens to requests when Redis is unavailable

This guide installs and configures Redis for caching on a Linux server, connects Django with the built-in backend, and verifies the setup.

Redis as the Django cacheDjango processesshared cache APIRedismaxmemory, LRUCached valueskeys with timeoutsFallbackrecompute on missOne shared cache keeps every worker consistent; eviction policy makes it self-managing.

Quick answer

  1. Install Redis, bind it to localhost or a private interface, and require a password or ACL user.
  2. Set maxmemory and maxmemory-policy allkeys-lru for a cache-only instance.
  3. Install the redis Python package (optionally hiredis).
  4. Configure CACHES with django.core.cache.backends.redis.RedisCache, a LOCATION URL, a KEY_PREFIX, and a default TIMEOUT.
  5. Keep Celery broker data in a separate Redis instance or at least a separate database with a non-evicting policy.
  6. Optionally use SESSION_ENGINE = "django.contrib.sessions.backends.cached_db".
  7. Verify with cache.set/cache.get from a Django shell and redis-cli INFO.

Step-by-step solution

1. Install Redis

On Ubuntu or Debian:

sudo apt update
sudo apt install redis-server
redis-server --version

Managed Redis-compatible services (ElastiCache, Memorystore, Upstash, and others) work with the same Django configuration; skip to step 4 and use the connection URL they provide.

2. Secure and size Redis

Edit /etc/redis/redis.conf:

bind 127.0.0.1 -::1
protected-mode yes
port 6379

requirepass use-a-long-generated-password

maxmemory 512mb
maxmemory-policy allkeys-lru
a cache Redis, set up rightBound to a private addressprotected-mode onPassword or ACL userin the Django URLmaxmemory setbelow the RAM you can sparemaxmemory-policy allkeys-lruevict, never refusePersistence offa cache may start emptyWithout maxmemory, Redis grows until the OOM killer steps indjango-deployment.com
Check the policy with CONFIG GET maxmemory-policy.
  • bind keeps Redis off public interfaces. If app servers are separate machines, bind to the private network address and allow the port only from those servers in the firewall.
  • requirepass sets the password for the default user. For finer control, define ACL users instead.
  • maxmemory caps memory use. Without it, Redis grows until the operating system runs out of memory.
  • allkeys-lru evicts the least recently used keys when the limit is reached, which is the right behavior for a pure cache.

On Ubuntu, the packaged service uses systemd supervision. Restart and check:

sudo systemctl restart redis-server
redis-cli -a 'use-a-long-generated-password' --no-auth-warning ping

Expect PONG.

3. Decide how cache and broker data coexist

A cache can lose any key at any time without harm. A Celery broker cannot: evicting queued task messages loses work. Because maxmemory-policy applies to the whole Redis instance, not per database number, a cache-friendly allkeys-lru policy is dangerous for broker data in the same instance.

cache and broker together?Does the same Redis alsocarry Celery or sessions?no, cache onlyOne instanceallkeys-lruyes, productionSeparate instancesLRU cache, noeviction brokeryes, tiny or devDB numbersshared memory and policyEviction policy is per instance, not per database numberdjango-deployment.com
Two small instances are cheap compared with lost tasks.

Recommended options, from most to least robust:

  1. Separate Redis instances for cache (allkeys-lru) and broker (noeviction).
  2. A managed service with separate cache and queue resources.
  3. One instance with noeviction and cache keys that always have timeouts, accepting that a full Redis rejects writes instead of evicting.

4. Install the client

redis==6.4.0
hiredis==3.2.1

hiredis is an optional C parser that speeds up response parsing; Django's backend uses it automatically when installed.

5. Configure Django

import os

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": os.environ["REDIS_CACHE_URL"],
        "KEY_PREFIX": "myapp",
        "TIMEOUT": 300,
        "OPTIONS": {
            "socket_connect_timeout": 2,
            "socket_timeout": 2,
        },
    }
}
the CACHES setting"BACKEND": "django.core.cache.backends.redis.RedisCache","LOCATION": os.environ["REDIS_CACHE_URL"],"KEY_PREFIX": "myapp","TIMEOUT":300,12341Built into Django 4.0+; no extra package.2Password and DB number live in the URL.3Keeps several apps from colliding.4Default lifetime; None means forever.django-redis adds extras such as IGNORE_EXCEPTIONSdjango-deployment.com
Changing KEY_PREFIX on deploy is a cheap way to start with an empty cache.

With the environment variable:

REDIS_CACHE_URL=redis://:use-a-long-generated-password@127.0.0.1:6379/0

Settings explained:

  • LOCATION accepts redis://, rediss:// (TLS), and unix:// URLs. For a managed service with TLS, use rediss://.
  • KEY_PREFIX namespaces keys, so several apps or environments can share a Redis database without collisions.
  • TIMEOUT is the default expiry in seconds. None means keys never expire, which is rarely what you want for a cache.
  • OPTIONS are passed to the redis-py connection pool. Short socket timeouts stop a hung Redis from blocking Gunicorn workers for long periods.

If a password contains characters such as @, :, or /, URL-encode it in the connection URL.

6. Replicas and read scaling

Django's Redis backend accepts a list of URLs. The first server is used for writes, and reads are distributed across the others:

"LOCATION": [
    "redis://:password@redis-primary:6379/0",
    "redis://:password@redis-replica-1:6379/0",
],

Replica reads can return slightly stale values. Most Django sites do not need this; a single Redis handles a large volume of cache traffic.

7. Cached sessions

Store sessions in the cache with a database fallback:

SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"

cached_db writes sessions to the database and caches reads, so an eviction or Redis restart does not log users out. The pure cache session engine is faster but loses sessions whenever keys are evicted, which will happen under allkeys-lru when memory is full.

8. Use the cache in code

from django.core.cache import cache


def get_dashboard_stats(account_id):
    key = f"dashboard-stats:{account_id}"
    stats = cache.get(key)
    if stats is None:
        stats = compute_stats(account_id)
        cache.set(key, stats, timeout=120)
    return stats

Cache values are pickled. Keep them small and avoid caching objects that hold database connections or file handles.

9. Failure behavior

If Redis is down, cache calls raise redis.exceptions.ConnectionError, which becomes a 500 in any view that uses the cache. Decide explicitly:

  • For pure performance caches, wrap reads and writes so a cache failure falls back to computing the value and logs a warning.
  • For features that require the cache (throttling, cache-based locks), failing is often correct.
decide what a cache outage doesOn Redis failureBuilt-in RedisCache✕exception: request failsdjango-redis IGNORE_EXCEPTIONS✓treated as a missSessions: cache engine✕users logged out, errorsSessions: cached_db✓falls back to the databaseA cache should make the site faster, never able to take it downdjango-deployment.com
Short socket timeouts keep a slow Redis from hanging workers.
import logging

from django.core.cache import cache
from redis.exceptions import RedisError

logger = logging.getLogger(__name__)


def cache_get_or_compute(key, compute, timeout=300):
    try:
        value = cache.get(key)
    except RedisError:
        logger.warning("cache read failed for %s", key)
        return compute()
    if value is None:
        value = compute()
        try:
            cache.set(key, value, timeout)
        except RedisError:
            logger.warning("cache write failed for %s", key)
    return value

10. Verify

python manage.py shell -c "
from django.core.cache import cache
cache.set('deploy-check', 'ok', 30)
print(cache.get('deploy-check'))
"

redis-cli -a 'use-a-long-generated-password' --no-auth-warning --scan --pattern 'myapp:*' | head
redis-cli -a 'use-a-long-generated-password' --no-auth-warning INFO memory | grep -E 'used_memory_human|maxmemory_human|maxmemory_policy'
redis-cli -a 'use-a-long-generated-password' --no-auth-warning INFO stats | grep -E 'keyspace_hits|keyspace_misses|evicted_keys'

Verification check:

  • the shell prints ok
  • keys appear with your prefix (Django adds a version segment, for example myapp:1:deploy-check)
  • maxmemory_policy shows allkeys-lru on the cache instance
  • keyspace_hits grows as traffic flows, and evicted_keys stays low relative to traffic
  • Redis is not reachable from outside: redis-cli -h PUBLIC_IP ping from another network fails

Explanation

A shared cache only helps if every process sees the same data and the cache can never become a source of truth. Redis provides the shared store; maxmemory with an LRU policy turns it into a self-managing cache; key prefixes and timeouts keep data organized and bounded. Keeping queues and caches apart matters because the eviction behavior that makes a cache healthy destroys a queue.

Edge cases / notes

Clearing the cache on deploy

cache.clear() flushes the entire Redis database, including other apps' keys if they share it. Prefer versioned keys (VERSION or a release-specific KEY_PREFIX) when a deploy changes cached data formats.

Large values

Redis handles large values, but serializing multi-megabyte objects on every request is slow. Cache smaller computed results or use HTTP caching for full pages.

Transparent huge pages warning

Redis may log a warning about transparent huge pages on some Linux distributions. Follow the remediation in the Redis log message; it affects latency under memory pressure.

Troubleshooting

Error or symptomCauseFix
ConnectionError: Error 111 connecting to 127.0.0.1:6379. Connection refused.Redis not running or bound elsewheresystemctl status redis-server, check bind
AuthenticationError or NOAUTH Authentication requiredpassword missing or not URL-encodedinclude the password in the URL; encode special characters
TimeoutError under loadRedis CPU saturated or network latencycheck INFO stats and SLOWLOG GET 10; avoid huge values
OOM command not allowed when used memory > 'maxmemory'noeviction policy and memory fullset allkeys-lru for cache instances or raise maxmemory
Cache hit rate near zerokeys expire too quickly or version/prefix changes every deployreview TIMEOUT, KEY_PREFIX, and VERSION
Stale data after deploycached format changed, keys not invalidatedbump VERSION in CACHES or the key prefix

Useful Redis commands

redis-cli -a "$REDIS_PASSWORD" --no-auth-warning INFO clients
redis-cli -a "$REDIS_PASSWORD" --no-auth-warning SLOWLOG GET 10
redis-cli -a "$REDIS_PASSWORD" --no-auth-warning --bigkeys
redis-cli -a "$REDIS_PASSWORD" --no-auth-warning MEMORY USAGE "myapp:1:dashboard-stats:42"
  • INFO clients shows connected clients; a steadily rising number can indicate connection leaks.
  • SLOWLOG GET lists recent slow commands.
  • --bigkeys samples the keyspace to find unusually large values.
  • Avoid KEYS * in production; it blocks Redis while scanning. Use --scan instead.

Passing the password with -a exposes it in shell history and process listings. For regular use, set the REDISCLI_AUTH environment variable in your session instead.

Monitoring to set up

what to watch on a cacheHealthy✓hit ratio steady✓evictions low and flat✓latency under a millisecondWarning signs✕evicted_keys climbing fast✕hit ratio falling✕rejected_connections above 0INFO stats and INFO memory show all of thesedjango-deployment.com
Fast-rising evictions mean the cache is too small for its working set.
  • memory used versus maxmemory
  • evicted keys per minute
  • hit ratio: keyspace_hits / (keyspace_hits + keyspace_misses)
  • connected clients
  • latency (redis-cli --latency for quick checks, exporter metrics for dashboards)

A falling hit ratio after a deploy is often the first sign that a code change stopped using the cache correctly.

Persistence settings for a cache

A pure cache does not need to survive restarts. Disabling persistence avoids disk I/O spikes during snapshots:

save ""
appendonly no

Only do this on a cache-only instance. A broker or session store that must survive restarts needs persistence enabled.

Security notes

  • Never expose Redis directly to the internet, even with a password; brute-force and exploitation attempts against open Redis ports are constant.
  • Rename or disable dangerous commands for application users with ACLs (for example deny FLUSHALL, CONFIG, and DEBUG).
  • Use TLS (rediss://) whenever traffic crosses a network you do not fully control.

Next steps

FAQ

Do I need django-redis, or is Django's built-in Redis cache enough?

Django's built-in backend covers standard cache operations and is enough for most sites. Third-party backends add features such as compression options, raw client access helpers, and some advanced connection settings.

Can Celery and the Django cache use the same Redis?

They can, but the eviction policy applies to the whole instance. A cache policy like allkeys-lru can evict queued tasks. Use separate instances or a non-evicting policy with timeouts on all cache keys.

What maxmemory-policy should a Django cache use?

allkeys-lru for a dedicated cache instance. It evicts the least recently used keys across the whole dataset when memory is full.

Why does my site return 500 errors when Redis restarts?

Cache operations raise connection errors when Redis is unavailable. Wrap non-critical cache access in error handling, and use cached_db sessions so users stay logged in.

Keep going