Configure Django Sessions for Production
Choose and tune Django's session backend for production: database, cached_db, cache, or signed cookies; cookie security flags; expiry; write frequency; and cleaning up expired sessions.
Problem statement
Django's session defaults work, but they are not tuned for production. The database backend adds a query per request and an ever-growing django_session table; the cache backend can log everyone out when the cache restarts; signed-cookie sessions grow headers and cannot be revoked server-side; and cookie flags decide whether sessions can be stolen over plain HTTP or read by scripts.
This guide chooses a session backend for your situation, sets the security flags, controls how often sessions are written, and keeps the session store from growing without bound.
- Use
django.contrib.sessions.backends.cached_dbfor most sites: reads from cache, durable in the database. - Set
SESSION_COOKIE_SECURE = True, keepSESSION_COOKIE_HTTPONLY = True, and useSESSION_COOKIE_SAMESITE = "Lax"unless you need cross-site requests. - Choose
SESSION_COOKIE_AGEfrom your security needs (the default is two weeks). - Leave
SESSION_SAVE_EVERY_REQUEST = Falseunless you need sliding expiry, and understand its write cost. - Run
python manage.py clearsessionsdaily for database-backed sessions. - Store only small, serialisable data in sessions; put larger state in models.
Step-by-step solution
1. Choose the engine
| Engine | Reads | Writes | Survives cache loss | Server-side revocation | Notes |
|---|---|---|---|---|---|
db (default) | DB query per request | DB write when modified | yes | yes | simple, extra query per request |
cached_db | cache, DB on miss | cache and DB | yes | yes | best general choice |
cache | cache | cache | no | yes | fastest; logins lost when cache is cleared or evicts |
signed_cookies | cookie | cookie | yes | no | no server storage; data readable by the user |
file | local disk | local disk | yes | yes | single server only |
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
cached_db writes through to the database and reads from the cache, so a Redis restart costs only a few cache misses rather than every login. It requires a real shared cache (Redis or Memcached); with the default local-memory cache, each process has its own copy.
Pure cache sessions are acceptable when the cache is dedicated to sessions, persistent, and not configured to evict (see the Redis guides). Signed cookies suit stateless deployments with tiny sessions, but you cannot log a user out everywhere without rotating SECRET_KEY, and the data (though tamper-proof) is visible to the user.
2. Set cookie flags
SESSION_COOKIE_SECURE = True # only sent over HTTPS
SESSION_COOKIE_HTTPONLY = True # default; not readable by JavaScript
SESSION_COOKIE_SAMESITE = "Lax" # default; not sent on cross-site subrequests
SESSION_COOKIE_NAME = "sessionid" # changing it logs everyone out
SESSION_COOKIE_DOMAIN = None # host-only unless you need subdomain sharing
SESSION_COOKIE_SECURE requires Django to see requests as HTTPS behind your proxy (SECURE_PROXY_SSL_HEADER), or sessions will appear not to persist. SameSite=Lax blocks most cross-site request forgery vectors while keeping top-level navigation logins working. Use "None" (with Secure) only if the site must be used inside cross-site iframes or receives cross-site POSTs that need the session.
3. Set lifetime
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 # one week
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
Shorter sessions reduce the window for stolen cookies; longer ones reduce login friction. Admin-heavy or financial applications often use hours, not weeks. For per-user choices ("remember me"), set the expiry at login:
if not form.cleaned_data.get("remember_me"):
request.session.set_expiry(0) # expire at browser close
4. Control write frequency
Django saves the session only when it is modified. With SESSION_SAVE_EVERY_REQUEST = True, every request writes the session (database write plus cache write with cached_db) to extend its expiry. That gives sliding expiration at the cost of one write per request, which on a busy site means constant write load on django_session.
A middle ground is to refresh occasionally:
import time
class RefreshSessionMiddleware:
INTERVAL = 60 * 60 # at most once per hour
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if request.user.is_authenticated:
last = request.session.get("_refreshed", 0)
if time.time() - last > self.INTERVAL:
request.session["_refreshed"] = int(time.time())
return response
Modifying the session marks it for saving, which renews its expiry.
5. Clean up expired sessions
Database-backed sessions are not deleted when they expire; rows accumulate. Schedule cleanup:
# /etc/systemd/system/myapp-clearsessions.service
[Service]
Type=oneshot
User=myapp
WorkingDirectory=/srv/myapp/current
EnvironmentFile=/etc/myapp.env
ExecStart=/srv/myapp/current/.venv/bin/python manage.py clearsessions
# /etc/systemd/system/myapp-clearsessions.timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Cache-backed sessions expire in the cache automatically.
6. Keep sessions small
Sessions are serialised as JSON (SESSION_SERIALIZER defaults to the JSON serializer), so only JSON-compatible data can be stored. Store identifiers, not objects: a cart ID, not the cart contents; a wizard step number, not uploaded file data. Large sessions slow down every request and, with signed cookies, can exceed header size limits.
7. Security events
- Django rotates the session key at login (
cycle_key) to prevent session fixation. - After a password change in your own views, call
update_session_auth_hash(request, user)to keep the current session valid while other sessions are invalidated. - On logout,
logout(request)flushes the session.
Verification check:
python manage.py shell -c "from django.conf import settings as s; print(s.SESSION_ENGINE, s.SESSION_COOKIE_SECURE, s.SESSION_COOKIE_HTTPONLY, s.SESSION_COOKIE_SAMESITE, s.SESSION_COOKIE_AGE)"
curl -sI -X POST https://example.com/accounts/login/ | grep -i set-cookie
python manage.py check --deploy
After logging in through the browser, the sessionid cookie should show Secure, HttpOnly, and SameSite=Lax. check --deploy warns if SESSION_COOKIE_SECURE is off.
Explanation
The session middleware loads session data lazily when code accesses request.session, and saves it after the response if it changed. The engine determines where that load and save go: a database query, a cache round-trip, or the cookie itself. That choice drives both performance (a query or a cache hit per authenticated request) and resilience (what a cache flush or key rotation does to logged-in users). Cookie flags determine how the session identifier travels, which is what attackers target.
Edge cases / notes
Multiple servers
Every server must use the same SECRET_KEY and a shared store. file sessions and local-memory caches only work on a single server.
Session data after deploys
Code changes that alter the shape of session data (renamed keys, new structures) must tolerate old sessions, which may be days old. Read with .get() and defaults.
When to automate this
Enforce session settings with check --deploy in CI, schedule clearsessions in provisioning, and monitor the size of the django_session table. Growth there usually means cleanup is not running.
Next steps
- Avoid mass logouts with Fix Users Logged Out After a Django Deploy.
- Set up the cache for
cached_dbwith Set Up Redis Caching for Django in Production. - Review related settings in the Django Production Settings Checklist (DEBUG, ALLOWED_HOSTS, CSRF).
- If logins do not stick behind HTTPS, see Fix Django Login Not Persisting Behind HTTPS.
FAQ
Which Django session backend is best for production?
cached_db suits most sites: fast reads from the cache and durability in the database.
Should I use SESSION_SAVE_EVERY_REQUEST?
Only if you need sliding expiry and can afford a session write on every request. Periodic refresh is a cheaper alternative.
How do I delete expired Django sessions?
Run python manage.py clearsessions on a schedule. Cache-backed sessions expire automatically.
Are signed cookie sessions secure?
They cannot be tampered with, but users can read the data, and you cannot revoke sessions server-side without rotating the secret key.