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.
Quick answer
- Install Redis, bind it to localhost or a private interface, and require a password or ACL user.
- Set
maxmemoryandmaxmemory-policy allkeys-lrufor a cache-only instance. - Install the
redisPython package (optionallyhiredis). - Configure
CACHESwithdjango.core.cache.backends.redis.RedisCache, aLOCATIONURL, aKEY_PREFIX, and a defaultTIMEOUT. - Keep Celery broker data in a separate Redis instance or at least a separate database with a non-evicting policy.
- Optionally use
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db". - Verify with
cache.set/cache.getfrom a Django shell andredis-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
bindkeeps 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.requirepasssets the password for the default user. For finer control, define ACL users instead.maxmemorycaps memory use. Without it, Redis grows until the operating system runs out of memory.allkeys-lruevicts 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.
Recommended options, from most to least robust:
- Separate Redis instances for cache (
allkeys-lru) and broker (noeviction). - A managed service with separate cache and queue resources.
- One instance with
noevictionand 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,
},
}
}
With the environment variable:
REDIS_CACHE_URL=redis://:use-a-long-generated-password@127.0.0.1:6379/0
Settings explained:
LOCATIONacceptsredis://,rediss://(TLS), andunix://URLs. For a managed service with TLS, userediss://.KEY_PREFIXnamespaces keys, so several apps or environments can share a Redis database without collisions.TIMEOUTis the default expiry in seconds.Nonemeans keys never expire, which is rarely what you want for a cache.OPTIONSare 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.
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_policyshowsallkeys-lruon the cache instancekeyspace_hitsgrows as traffic flows, andevicted_keysstays low relative to traffic- Redis is not reachable from outside:
redis-cli -h PUBLIC_IP pingfrom 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 symptom | Cause | Fix |
|---|---|---|
ConnectionError: Error 111 connecting to 127.0.0.1:6379. Connection refused. | Redis not running or bound elsewhere | systemctl status redis-server, check bind |
AuthenticationError or NOAUTH Authentication required | password missing or not URL-encoded | include the password in the URL; encode special characters |
TimeoutError under load | Redis CPU saturated or network latency | check INFO stats and SLOWLOG GET 10; avoid huge values |
OOM command not allowed when used memory > 'maxmemory' | noeviction policy and memory full | set allkeys-lru for cache instances or raise maxmemory |
| Cache hit rate near zero | keys expire too quickly or version/prefix changes every deploy | review TIMEOUT, KEY_PREFIX, and VERSION |
| Stale data after deploy | cached format changed, keys not invalidated | bump 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 clientsshows connected clients; a steadily rising number can indicate connection leaks.SLOWLOG GETlists recent slow commands.--bigkeyssamples the keyspace to find unusually large values.- Avoid
KEYS *in production; it blocks Redis while scanning. Use--scaninstead.
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
- memory used versus
maxmemory - evicted keys per minute
- hit ratio:
keyspace_hits / (keyspace_hits + keyspace_misses) - connected clients
- latency (
redis-cli --latencyfor 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, andDEBUG). - Use TLS (
rediss://) whenever traffic crosses a network you do not fully control.
Next steps
- Decide what to cache with Django Caching Strategy for Production.
- If connections fail, see Fix Redis Connection Refused in Django.
- Configure the queue side in Configure Redis and Celery for Django Production.
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.