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.

What a login depends onSession cookiename, domainSession storeDB, cache, cookieSECRET_KEYsigns and verifiesAuth hashmust still matchChange any of these during a deploy and every user's session becomes invalid at once.
  1. Check whether SECRET_KEY changed: a key generated at start-up, a new value in the environment, or different keys on different servers.
  2. Keep the key stable and identical everywhere; when rotating, move the old key to SECRET_KEY_FALLBACKS.
  3. Check the session backend: locmem cache sessions vanish on restart, and Redis without persistence loses them when Redis restarts.
  4. Use database or cached_db sessions, or a persistent Redis dedicated to sessions.
  5. Do not change SESSION_COOKIE_NAME, SESSION_COOKIE_DOMAIN, or SESSION_COOKIE_PATH casually; each change orphans existing cookies.
  6. 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())
a new key invalidates every sessionUnstable key✕generated at start-up✕different per server or container✕every deploy logs everyone outStable key✓from the environment or a secret store✓identical on every instance✓changed only deliberatelySessions and signed cookies are signed with SECRET_KEYdjango-deployment.com
Compare a hash of the key across instances, never the key itself.

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"]]
rotate without logging anyone outGenerate new keykeep the old oneDeploynew key + old in FALLBACKSOld signatures verifynew ones use the new keyWaitSESSION_COOKIE_AGERemove the fallbackrotation completePassword reset tokens also survive the rotation this waydjango-deployment.com
Available since Django 4.1.

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'])"
does the session store survive?Survives a deploy?db✓yescached_db✓yes, database backs itcache: locmem✕no: lives in the processcache: Redis!yes, unless flushed or evictedfile in the release dir✕no: new directory each releaseSigned cookie sessions survive only if SECRET_KEY stays the samedjango-deployment.com
A deploy script that runs FLUSHALL also logs everyone out.
EngineWhere data livesSurvives deploys?
dbdatabase tableyes
cached_dbcache plus databaseyes, database is the source of truth
cache with LocMemCachememory of each processno, lost on every restart and not shared between workers
cache with Redis or Memcachedcache serveronly if the cache server keeps data across its own restarts and does not evict it
signed_cookiesthe browser cookieyes, 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"

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).

settings that must not driftSESSION_COOKIE_NAMErenaming orphans every cookieSESSION_COOKIE_DOMAIN and PATHbrowser keeps the old cookieCACHES KEY_PREFIX and VERSIONcache sessions look missingSECRET_KEYor use fallbacksChanging any of these is a deliberate, announced logoutdjango-deployment.com
Diff settings between releases as part of review.

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

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.

Keep going