Fix Django "Session data corrupted" Warnings

Fix the Django warning Session data corrupted and users being logged out: find SECRET_KEY changes and mismatches between servers, use SECRET_KEY_FALLBACKS for rotation, and align session engines and serializers.

Problem statement

The logs show this warning, often in bursts after a deploy:

WARNING django.security.SuspiciousSession: Session data corrupted

and users report being logged out or losing their cart. Django signs session data with SECRET_KEY. When it reads a session and the signature does not verify, it treats the data as tampered with, logs this warning, and starts an empty session. Real tampering is rare. The usual cause is that the key used to sign the session is not the key used to read it: the secret changed, servers disagree about it, or the serialization format changed.

Why a session fails to verifySession writtensigned with key AKey changesrotation or mismatchSession readverified with key BSignature failsempty session, logoutSessions signed with one SECRET_KEY cannot be read by a process using a different one.
  1. Check that every web and worker process uses the same SECRET_KEY (compare a hash of it, not the value).
  2. If the key was rotated, add the old key to SECRET_KEY_FALLBACKS (Django 4.1+) for one session lifetime.
  3. Make sure the key is not generated at startup (for example get_random_secret_key() in settings).
  4. Keep SESSION_SERIALIZER and SESSION_ENGINE consistent across servers during deploys.
  5. If the warning is isolated and rare, it may be a stale or malformed cookie; no action needed.

Step-by-step solution

1. Compare keys across processes safely

Never print the secret. Print a short hash from each process:

python manage.py shell -c "import hashlib; from django.conf import settings; print(hashlib.sha256(settings.SECRET_KEY.encode()).hexdigest()[:12])"

Run it on every server, in every container, and in Celery workers if they create sessions. Different hashes mean different keys: a missing environment variable on one host, a Secret updated on only some pods, or a settings module that falls back to a default.

2. Never generate the key at startup

This pattern logs everyone out on every restart, and with several Gunicorn workers each worker has its own key:

SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key())   # broken

Fail loudly instead:

SECRET_KEY = os.environ["SECRET_KEY"]
pattern to causePatternCause and fixburst right after a deploykey rotated; add old keyto SECRET_KEY_FALLBACKSrandom users, all the timeservers or workersdisagree on SECRET_KEYeveryone out on each restartkey generated at startup;read it from envafter changing SESSION_SERIALIZERold sessions unreadable;plan the switchrare and isolatedstale or edited cookie; harmlessTiming of the warnings tells you which cause appliesdjango-deployment.com
Compare key hashes across every process before changing anything.

3. Rotate keys with fallbacks

Since Django 4.1, SECRET_KEY_FALLBACKS lets Django verify signatures made with older keys while signing new data with the current one:

SECRET_KEY = os.environ["SECRET_KEY"]
SECRET_KEY_FALLBACKS = [k for k in os.environ.get("SECRET_KEY_FALLBACKS", "").split(",") if k]

Deploy the new key with the old one in the fallbacks, keep it there for at least SESSION_COOKIE_AGE (two weeks by default), then remove it. Sessions, password reset tokens, and signed cookies keep working through the rotation. See Rotate Django SECRET_KEY Safely.

4. Keep session settings consistent during deploys

During a rolling deploy, old and new versions run at the same time. If the new version changes SESSION_ENGINE (for example from database to cache) or SESSION_SERIALIZER, sessions created by one version may be unreadable by the other. Change these in two steps, or accept a logout for active users and announce it.

With the signed_cookies engine, the whole session lives in the cookie and every verification depends on the key, so mismatches show up immediately and widely.

5. Decide if it is noise

A single warning now and then can come from a user with a very old cookie, a browser extension, or a bot sending junk cookies. If the hashes match across processes and nothing changed recently, it is safe to leave. Route django.security.SuspiciousSession to its own logger if you want to monitor volume without noise.

rotation with SECRET_KEY_FALLBACKSGenerate new keystore as a secretDeploy new + fallbackold key in FALLBACKSOld sessions stillverifynew ones signed with new keyWaitSESSION_COOKIE_AGEtwo weeks by defaultRemove old keynext deployFallbacks let both keys verify while only the new key signsdjango-deployment.com
If the old key leaked, skip the waiting period and accept the logouts.

Verification check: after fixing, the count of Session data corrupted warnings should drop to near zero, and logging in on one server then hitting another (or restarting Gunicorn) should keep you logged in.

Explanation

Django serializes session data and signs it with an HMAC derived from SECRET_KEY and a salt. On read, it recomputes the signature and compares. Any change to the key, the salt, or the data produces a mismatch, which Django cannot distinguish from tampering, so it logs a SuspiciousSession and returns an empty session rather than trusting the data. For database and cache backends the signed value is stored server-side, but it is still verified with the key, which is why key mismatches affect every session engine.

Edge cases / notes

  • Leaked key. If the old key was exposed, do not put it in fallbacks; rotate immediately and accept that users are logged out.
  • Cache eviction. With the cache session engine, evicted sessions simply disappear (no warning) and users are logged out; use cached_db for durability.
  • Multiple apps on one domain sharing a cookie name can overwrite each other's session cookies; set distinct SESSION_COOKIE_NAME values.
  • Celery tasks rarely touch sessions, but if they sign data (tokens), they need the same key and fallbacks.

Next steps

FAQ

Does Session data corrupted mean I was hacked?

Rarely. It usually means the signing key changed or differs between servers.

How do I change SECRET_KEY without logging everyone out?

Use SECRET_KEY_FALLBACKS with the old key for one session lifetime, then remove it.

Why are users logged out after every restart?

The key is probably generated at startup. Load a fixed key from the environment.

Can I silence the warning?

You can filter the logger, but first confirm all processes share the same key.

Keep going