Fix Users Logged Out After a Django Deploy
Stop Django deploys from logging everyone out: a changed or per-process SECRET_KEY, cache-backed sessions lost on restart, changed cookie settings, and rotating keys safely with SECRET_KEY_FALLBACKS.
Problem statement
Every deploy (or every restart, or sometimes at random) logs all users out. Support tickets arrive about lost carts and interrupted checkouts, and staff have to log in to the admin again after each release.
A Django login lives in two places: a session cookie in the browser, and session data on the server (or in the cookie itself, for signed-cookie sessions). The session also stores a hash derived from the user's password hash and the SECRET_KEY. Users are logged out when the server can no longer find the session, can no longer verify it, or the browser stops sending the cookie. Each of those has a deploy-related cause, listed below with its fix.
- Check whether
SECRET_KEYchanged: a key generated at start-up, a new value in the environment, or different keys on different servers. - Keep the key stable and identical everywhere; when rotating, move the old key to
SECRET_KEY_FALLBACKS. - Check the session backend:
locmemcache sessions vanish on restart, and Redis without persistence loses them when Redis restarts. - Use database or
cached_dbsessions, or a persistent Redis dedicated to sessions. - Do not change
SESSION_COOKIE_NAME,SESSION_COOKIE_DOMAIN, orSESSION_COOKIE_PATHcasually; each change orphans existing cookies. - Confirm the cache key prefix or version is not changed on every release.
Step-by-step solution
1. Is the SECRET_KEY stable?
The most common cause is a settings line like this:
from django.core.management.utils import get_random_secret_key
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", get_random_secret_key())
If the environment variable is missing in production, every process start generates a new key. Every restart invalidates every session, and with several Gunicorn workers or servers each has its own key, which logs users out randomly as requests hit different processes.
Fail loudly instead:
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
Check that all processes use the same key without printing it:
python -c "import hashlib, os; print(hashlib.sha256(os.environ['DJANGO_SECRET_KEY'].encode()).hexdigest()[:12])"
Run it with each server's and each service's environment (web, workers) and compare the fingerprints.
2. Rotate keys without logging users out
If the key changed on purpose (rotation after a leak, or a new secret store), Django can accept the old key for verification while signing with the new one:
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
SECRET_KEY_FALLBACKS = [os.environ["DJANGO_SECRET_KEY_OLD"]]
Sessions and other signed values created with the old key keep working; new ones use the new key. Some older Django releases did not consult the fallbacks when verifying the session auth hash, so test a rotation on your version (log in, rotate, reload) before relying on it. Remove the fallback after your session lifetime (SESSION_COOKIE_AGE, two weeks by default) has passed. If the rotation is because the old key leaked, you may prefer to log everyone out deliberately rather than keep trusting it.
3. Is the session store surviving the deploy?
python manage.py shell -c "from django.conf import settings; print(settings.SESSION_ENGINE, settings.CACHES['default']['BACKEND'])"
| Engine | Where data lives | Survives deploys? |
|---|---|---|
db | database table | yes |
cached_db | cache plus database | yes, database is the source of truth |
cache with LocMemCache | memory of each process | no, lost on every restart and not shared between workers |
cache with Redis or Memcached | cache server | only if the cache server keeps data across its own restarts and does not evict it |
signed_cookies | the browser cookie | yes, as long as SECRET_KEY is stable |
cache sessions with LocMemCache are a frequent accident: the default cache backend is local memory, so enabling cache sessions without configuring a real cache gives each worker its own session store. Switch to cached_db for most sites:
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
With Redis-backed sessions, check that Redis is not restarted as part of the deploy, has persistence if you rely on it, and is not configured with an eviction policy that removes session keys under memory pressure.
4. Did the cache key prefix change?
If sessions are stored in a cache and you change KEY_PREFIX or VERSION in CACHES on each release (a common way to invalidate cached pages), session keys change too, and every session disappears. Use a separate cache alias for sessions with a fixed prefix:
CACHES = {
"default": {"BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": REDIS_URL, "KEY_PREFIX": f"v{RELEASE}"},
"sessions": {"BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": REDIS_URL, "KEY_PREFIX": "sessions"},
}
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
SESSION_CACHE_ALIAS = "sessions"
5. Did cookie settings change?
The browser sends the session cookie only if its name, domain, and path match. Changing SESSION_COOKIE_NAME, switching SESSION_COOKIE_DOMAIN from None to .example.com (or back), or changing SESSION_COOKIE_PATH makes existing cookies invisible to Django. Plan such changes as a one-time logout, and check that the host users visit did not change (for example a deploy that starts redirecting www to the apex with host-only cookies).
Enabling SESSION_COOKIE_SECURE on a site still served over HTTP to some users also makes logins disappear, because browsers do not send secure cookies over HTTP.
6. Password hash changes
The session auth hash is derived from the user's password hash. Changing a user's password logs out their other sessions (by design). A bulk operation that rewrites password hashes (for example a data migration that resets or rehashes passwords) logs those users out everywhere. Upgrading PASSWORD_HASHERS alone does not: Django rehashes passwords lazily on the user's next login.
Verification check:
Log in on a test account, then deploy or restart:
sudo systemctl restart gunicorn
curl -s -o /dev/null -w "%{http_code}\n" -b "sessionid=<value from browser>" https://example.com/accounts/profile/
Expect 200, not a redirect to the login page. Repeat across several requests to confirm every worker and server accepts the session.
Explanation
Django's session middleware looks up the session key from the cookie in the configured store, and the signing layer verifies stored data using SECRET_KEY (and its fallbacks). The authentication middleware then compares the auth hash in the session with one computed from the user's current password hash and the key. A deploy that changes the key, clears the store, or changes the cookie contract breaks one of these steps for every user at once, which is why mass logouts cluster around deploys and restarts.
Edge cases / notes
Remember-me and long sessions
With long SESSION_COOKIE_AGE, the database session table grows. Run python manage.py clearsessions on a schedule to remove expired rows.
Other signed data
Password reset links, signing.dumps() tokens, and some third-party tokens also use SECRET_KEY. SECRET_KEY_FALLBACKS keeps those valid during rotation too.
When to automate this
Add a deploy check that compares the secret key fingerprint across every server and service, and a post-deploy test that reuses a session cookie created before the deploy. Both catch mass logouts before users do.
Next steps
- Change keys safely with Rotate the Django SECRET_KEY Safely.
- Choose a durable session backend with Configure Django Sessions for Production.
- If logins fail immediately rather than after deploys, see Fix Django Login Not Persisting Behind HTTPS.
- Keep keys consistent across servers with Manage Django Secrets in Production.
FAQ
Why does every Django deploy log users out?
Usually the SECRET_KEY changes (often generated at start-up because the variable is missing), or sessions are stored in a cache that is cleared on restart.
How do I change SECRET_KEY without logging everyone out?
Set the new key as SECRET_KEY and put the old one in SECRET_KEY_FALLBACKS until existing sessions expire.
Why are users logged out randomly behind a load balancer?
Servers or processes use different secret keys or local-memory session stores. Use one key everywhere and a shared session backend.
Does restarting Redis log users out?
Yes, if sessions are stored only in Redis and it has no persistence. Use cached_db sessions or configure Redis persistence.