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.

Session request pathBrowser cookiesessionidSession enginedb, cache, cookieStore lookupper requestWrite backonly if modifiedThe engine decides where sessions live and what a cache restart or key change does to logins.
  1. Use django.contrib.sessions.backends.cached_db for most sites: reads from cache, durable in the database.
  2. Set SESSION_COOKIE_SECURE = True, keep SESSION_COOKIE_HTTPONLY = True, and use SESSION_COOKIE_SAMESITE = "Lax" unless you need cross-site requests.
  3. Choose SESSION_COOKIE_AGE from your security needs (the default is two weeks).
  4. Leave SESSION_SAVE_EVERY_REQUEST = False unless you need sliding expiry, and understand its write cost.
  5. Run python manage.py clearsessions daily for database-backed sessions.
  6. Store only small, serialisable data in sessions; put larger state in models.

Step-by-step solution

1. Choose the engine

EngineReadsWritesSurvives cache lossServer-side revocationNotes
db (default)DB query per requestDB write when modifiedyesyessimple, extra query per request
cached_dbcache, DB on misscache and DByesyesbest general choice
cachecachecachenoyesfastest; logins lost when cache is cleared or evicts
signed_cookiescookiecookieyesnono server storage; data readable by the user
filelocal disklocal diskyesyessingle 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.

pick the engine by what must surviveWhat does your deployment have, and need?no shared cachedbsimple, one query per requestRedis or Memcachedcached_dbfast, survives cache lossstateless, tiny datasigned_cookiesno server-side revocationPure cache sessions only make sense with a dedicated, persistent cachedjango-deployment.com
cached_db is the default recommendation because a cache restart then costs misses, not logins.

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.

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.

every attribute comes from a settingSet-Cookie: sessionid=4kz0q2v…;Path=/;Max-Age=604800;HttpOnly;SameSite=Lax;Secure12341SESSION_COOKIE_AGE: one week here; the default is two weeks.2SESSION_COOKIE_HTTPONLY: scripts cannot read it, so XSS cannot steal itdirectly.3SESSION_COOKIE_SAMESITE: not sent on cross-site subrequests, blocking most CSRFvectors.4SESSION_COOKIE_SECURE: HTTPS only. Behind a proxy it needsSECURE_PROXY_SSL_HEADER.Inspect the real header in the browser after logging indjango-deployment.com
A missing Secure flag on a live site usually means Django does not know the request was HTTPS.

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

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.

They cannot be tampered with, but users can read the data, and you cannot revoke sessions server-side without rotating the secret key.

Keep going