To share a login or session across subdomains in Django, set SESSION_COOKIE_DOMAIN = '.example.com' (with a leading dot) in settings.py. The leading dot tells the browser to scope the session cookie to the parent domain, so the same sessionid is sent to app.example.com, accounts.example.com, and every other subdomain. Pair that with one shared SECRET_KEY, a single shared session store, and matching CSRF settings, and a user who signs in on one subdomain stays signed in on all of them.
This is the standard pattern for the 'log in once, browse everywhere' experience inside a single registered domain. It does not work across two unrelated domains (example.com and partner.io) — for that you need real single sign-on across multiple applications. This guide covers the full Django 5.x configuration: the cookie, the shared session store, CSRF, local testing, and the security trade-offs.
Key takeaways
- Set
SESSION_COOKIE_DOMAIN = '.example.com'(leading dot) so the session cookie is shared with every subdomain. - Every subdomain deployment must use the same
SECRET_KEY, or sessions signed on one host fail validation on another. - Point all subdomains at one shared session store (
cached_db, Redis, or a shared database) so they read the same session data. - Mirror the cookie config for CSRF:
CSRF_COOKIE_DOMAINplusCSRF_TRUSTED_ORIGINS = ['https://*.example.com']. - In production set
SESSION_COOKIE_SECURE = Trueand chooseSESSION_COOKIE_SAMESITEdeliberately. - A wildcard cookie widens your blast radius — one XSS-prone or untrusted subdomain can leak the shared session.
How do cookies scope to subdomains?
Whether a cookie is shared depends on its Domain attribute. By default Django sets no Domain, which produces a host-only cookie — the browser returns it only to the exact host that set it. To share across subdomains you must explicitly set a domain cookie scoped to the parent.
| Cookie scope | Domain attribute |
Sent to example.com? |
Sent to app.example.com? |
When to use |
|---|---|---|---|---|
| Host-only (default) | none | Only the host that set it | No | Single-host apps; tightest scope |
| Domain cookie | .example.com |
Yes | Yes (all subdomains) | Shared login across subdomains |
A domain cookie is delivered to the parent and every subdomain at every depth, including a.b.example.com. Modern browsers treat Domain=example.com and Domain=.example.com the same way, but Django expects the leading-dot form and writing it explicitly keeps the intent clear.
If you also need per-subdomain routing, middleware, or tenanting alongside shared sessions, see working with Django subdomains.
How do you configure Django to share sessions across subdomains?
Add the following to the settings.py that every subdomain deployment loads:
# settings.py — shared by every *.example.com deployment
# 1) Identical on every subdomain, or signed sessions won't validate.
SECRET_KEY = 'your-identical-secret-key-across-all-subdomains'
# 2) Accept requests for the parent domain and all subdomains.
ALLOWED_HOSTS = ['.example.com'] # leading dot = example.com + *.example.com
# 3) The key line: scope the session cookie to the parent domain.
SESSION_COOKIE_DOMAIN = '.example.com' # note the leading dot
# 4) Production hardening.
SESSION_COOKIE_SECURE = True # HTTPS only
SESSION_COOKIE_HTTPONLY = True # JS can't read it (Django default)
SESSION_COOKIE_SAMESITE = 'Lax' # Lax is fine between same-site subdomainsThe leading dot on SESSION_COOKIE_DOMAIN is what makes the cookie travel across subdomains. ALLOWED_HOSTS = ['.example.com'] uses the same shorthand to authorise the parent domain and every subdomain in one entry. With this alone the cookie is shared — but the receiving subdomains still need to resolve that cookie to the same session data, which is the next step.
Why do all subdomains need one shared session store?
A shared cookie only carries the session key — the actual session data lives server-side. If app.example.com and accounts.example.com run as separate processes (or on separate servers), each with its own local session store, the cookie travels fine but the second host cannot find the session and treats the user as logged out.
Two requirements make it work:
- One identical
SECRET_KEYeverywhere. Sessions are signed; a different key means a different signature and Django rejects the session as tampered. - One shared session backend that every subdomain reads from and writes to.
Session backend (SESSION_ENGINE) |
Shared across servers? | Speed | Notes |
|---|---|---|---|
db (database) |
Yes, with one shared DB | Moderate | Default; simplest shared store |
cached_db |
Yes, with shared cache + DB | Fast reads | Cache read-through, DB as source of truth |
cache (Redis/Memcached) |
Yes, with a shared cache | Fastest | Use a persistent store like Redis |
signed_cookies |
N/A (no server store) | Fast | Cannot revoke; size-limited; avoid for auth |
Pick one shared backend — cached_db is a safe default, while Redis (cache) scales best. Because all subdomains hit the same database, related identity data such as a custom Django user model is shared automatically:
# Use ONE shared backend so every subdomain reads the same sessions.
# Option A — cached_db: read-through cache, database as source of truth.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'django_cache_table', # run: python manage.py createcachetable
}
}
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# Option B — Redis (recommended at scale). RedisCache is built in since Django 4.0.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://10.0.0.10:6379/1', # one Redis all subdomains share
}
}
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'What about CSRF across subdomains?
Sharing the session cookie is only half the job — Django also protects POST requests with a CSRF token, and that token check has its own cross-subdomain settings. Mirror the session config:
# Mirror the session config so CSRF-protected POSTs work across subdomains.
CSRF_COOKIE_DOMAIN = '.example.com'
CSRF_TRUSTED_ORIGINS = ['https://*.example.com'] # scheme required; wildcard OK (Django 4.0+)
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'Lax'Since Django 4.0, CSRF_TRUSTED_ORIGINS entries must include the scheme (https://) and may use a * wildcard for subdomains. CSRF_COOKIE_DOMAIN shares the CSRF token cookie the same way the session cookie is shared, so a form rendered on one subdomain can be submitted from another. Forgetting these two settings is the usual cause of 'CSRF verification failed' errors right after you enable cross-subdomain sessions.
How do you test subdomain sessions on localhost?
Browsers will not set a domain cookie for localhost or any name without a dot. Map a real dotted hostname to 127.0.0.1 in /etc/hosts (the reserved .test TLD is ideal):
127.0.0.1 example.test
127.0.0.1 app.example.test
127.0.0.1 accounts.example.testAdd those hosts to ALLOWED_HOSTS and, because local dev is usually plain HTTP, set SESSION_COOKIE_SECURE = False and CSRF_COOKIE_SECURE = False for local settings only. Then visit http://app.example.test:8000/, log in, and open http://accounts.example.test:8000/ — you should still be authenticated.
What are the security caveats?
Sharing a cookie across subdomains widens what a single weak link can damage. Lock it down:
SESSION_COOKIE_SECURE = True— never send the session cookie over plain HTTP in production.HttpOnlystays on by default — keep it so JavaScript cannot read the cookie (mitigates token theft via XSS).SESSION_COOKIE_SAMESITE—'Lax'is fine for navigation between subdomains of the same site. Use'Strict'for maximum protection, or'None'(which requiresSecure) only when a genuinely cross-site context needs the cookie.- Blast radius — every subdomain that receives the cookie can read the session. Never host untrusted, user-generated, or third-party content on a subdomain that shares your auth cookie; one XSS there compromises every session.
- Subdomain takeover — a dangling DNS record pointing at a deprovisioned host lets an attacker stand up a rogue
*.example.comthat the browser will happily hand your cookie to. Audit DNS regularly.
If you are revisiting authentication anyway, it is a good moment to evaluate passwordless authentication in Django to cut credential-phishing risk.
Shared session cookie vs token/JWT SSO vs separate logins
The wildcard-cookie approach is the simplest way to share auth inside one domain, but it is not the only option — and it is the wrong one once you cross to a different registered domain.
| Approach | Scope | Pros | Cons |
|---|---|---|---|
Shared session cookie (SESSION_COOKIE_DOMAIN) |
Subdomains of ONE domain | Trivial setup; native Django sessions; instant server-side revoke | Same-domain only; wide cookie blast radius |
| Token / JWT SSO | Any domain | Works cross-domain; stateless APIs | Revocation/refresh complexity; token storage is an XSS target |
| Central SSO (OAuth2 / OIDC / SAML) | Any domain or app | True enterprise SSO; one identity provider | Most moving parts; needs an IdP |
| Separate logins per subdomain | Each host isolated | Strongest isolation | Users re-authenticate everywhere; poor UX |
For sharing inside one domain, the cookie approach wins on simplicity. The moment you need to span two different registered domains, switch to real single sign-on for multiple applications — a wildcard cookie cannot bridge example.com and another-domain.com.
Getting subdomain sessions, CSRF, and a shared Redis store right — without opening a security hole — is fiddlier than it looks. If you would rather have it done correctly the first time, our Django development services team has shipped multi-tenant and SSO architectures across 50+ projects since 2014.
Frequently Asked Questions
How do I share a Django session across subdomains?
Set SESSION_COOKIE_DOMAIN = '.example.com' (with a leading dot) in settings.py, use the same SECRET_KEY on every subdomain, and point them all at one shared session store. The browser then sends the same sessionid cookie to every subdomain, so a single login covers them all.
Why does SESSION_COOKIE_DOMAIN need a leading dot?
The leading dot (.example.com) marks the cookie as a domain-wide cookie rather than a host-only one, so the browser sends it to the parent domain and every subdomain. Without it, Django issues a host-only cookie that only the exact host which set it receives.
Can I share a Django session across two different domains?
No. A cookie is scoped to a single registered domain, so SESSION_COOKIE_DOMAIN cannot bridge example.com and example.org. For cross-domain single sign-on you need a token-based or OAuth2/OIDC/SAML flow rather than a shared session cookie.
Do I need the same SECRET_KEY on every subdomain?
Yes. Django cryptographically signs session data with SECRET_KEY. If two subdomain deployments use different keys, a session created on one fails signature verification on the other and the user appears logged out. Keep one identical key across all deployments.
What CSRF settings do I need for subdomains?
Set CSRF_COOKIE_DOMAIN = '.example.com' so the CSRF cookie is shared, and add CSRF_TRUSTED_ORIGINS = ['https://*.example.com'] so Django accepts POSTs from any subdomain (Django 4.0+ requires the scheme and supports the wildcard). Also list your hosts in ALLOWED_HOSTS.
What are the security risks of a wildcard session cookie?
A domain cookie is sent to every subdomain, so any one of them can read the session. A compromised or untrusted subdomain — user-controlled *.example.com content or an XSS bug — can steal the shared sessionid. Mitigate with SESSION_COOKIE_SECURE, the default HttpOnly, a deliberate SameSite value, and by never hosting untrusted content on a subdomain that shares the cookie.