← Back to Blog
Odoo by Featured

Session Management in Odoo, Part 3: The FAQ

Session Management in Odoo, Part 3: The FAQ

Part 1 and Part 2 of this series covered how Odoo sessions work internally and which community modules extend them. This closing post takes the opposite angle: the questions we actually get asked, with short answers and pointers back to the detailed explanations. All answers apply to Odoo 19.0 unless a version is stated; most hold for 16.0 and later.


Lifetime and Expiry

How long does an Odoo session last?

Forever, as long as the user stays active. Odoo only expires sessions on inactivity: each request slides the deadline forward, so a session in daily use never dies. The default inactivity threshold is seven days since Odoo 16.0. Before 16.0 the numbers were inconsistent: the cookie was valid for 90 days but the server deleted inactive session files after 7, so 7 days was already the effective limit. Odoo 16.0 aligned both on 7 days [1].

There is no absolute lifetime cap — no native setting says “force re-login every N days regardless of activity.” If a compliance policy requires one, it takes custom code (covered further down in this section).

How do I change the session inactivity threshold?

Set the sessions.max_inactivity_seconds system parameter (Settings → Technical → System Parameters). It drives both the cookie Max-Age and the server-side cleanup threshold [2]. Two caveats:

  • Expired files are only deleted when the daily “Base: Auto-vacuum internal data” cron runs. If you set a short value, increase that cron’s frequency to match.
  • Odoo does not check session age at request time. A browser drops the cookie when it expires, but a script that keeps the cookie can use the session until the cron deletes the file.

Is it possible to cap the maximum lifetime of a session, regardless of activity?

Not natively. The only way to enforce a hard cap — “force re-login every 24 hours no matter what” — is a small custom module: stamp the session with the real login time at authentication, then check it on every request and force a logout once the cap is exceeded.

I set a short timeout but users stay logged in. Why?

Most likely the auto-vacuum cron has not run yet (see above), or the users are active: the timeout counts inactivity, not total session age. If you need a hard “log out after N minutes idle” behaviour enforced on the very next request, use the OCA module auth_session_timeout instead — it checks inactivity per request rather than waiting for the cron (see Part 2).

Why did my session ID change in the middle of the day?

That is soft rotation: every three hours of activity, Odoo regenerates the last half of the session ID while keeping the first 42 bytes stable [3]. It is a security refresh, invisible to the user, and CSRF tokens survive it precisely because the prefix is preserved.

Do POS sessions expire with these settings?

No — different concept entirely. A POS session is a business object (pos.session) that tracks a cash register’s opening and closing; it is unrelated to the HTTP sessions discussed in this series.


Logout and Invalidation

How do I force-logout one user?

Change their password or deactivate their account. Every session stores a session_token, an HMAC computed over the user’s login, hashed password, and active flag; on each request Odoo recomputes it from the live database, so any change to those inputs invalidates all of the user’s sessions immediately [4]. No cache flush, no server restart.

For remote force-logout via an API endpoint, without touching the password, watch the auth_session_logout_api module Trobz contributed to OCA/server-auth (pending merge, see Part 2).

How do I force-logout everyone at once?

Rotate the database.secret system parameter. It is the root key of every session token, so changing it invalidates all sessions for all users in the database instantly [4]. It is the emergency lever for a suspected compromise; expect every user to land back on the login page.

Does restarting Odoo log users out?

No. Sessions live on disk (or in PostgreSQL/Redis with the modules from Part 2), not in server memory. A restart, an upgrade, or a worker recycle does not touch them.

Can I see which devices or IPs are logged in for a user?

Partially. Odoo tracks a device log per session in res.device.log: platform, browser, IP, and first/last activity timestamps, referenced in Part 1’s “Connected devices” feature [7]. It is populated automatically on authenticated requests and is queryable like any other model, which makes it useful for security audits. What it does not give you out of the box is a self-service “log out this device” button — the revoked flag on each record is bookkeeping the framework updates itself when a session disappears, not a lever you flip to terminate one. To actually force a specific session out, fall back to the force-logout answer above (password change, or the pending auth_session_logout_api for surgical, password-free logout).


Storage and Operations

Where are session files and can I delete them?

{data-dir}/sessions/, scattered across up to 4096 two-character subdirectories, one JSON file per session [5]. Deleting a file is safe and simply logs that session out — deleting all of them is the crude version of rotating database.secret. Since 16.0 you should not need manual cleanup: the auto-vacuum cron reaps expired files daily.

My sessions directory keeps growing. Is that normal?

Some growth is normal: every visitor, health check, and API client that becomes “dirty” gets a session file. Files older than the inactivity threshold should be reaped daily by the auto-vacuum cron. If they are not, check that the cron is running, and check the ODOO_SKIP_GC_SESSIONS environment variable — when set, Odoo skips its built-in session GC entirely and expects something else to clean up [6].

Users get randomly logged out since we added a second server. What happened?

The filesystem session store is per-machine: a session created on node A does not exist on node B, so requests landing on the wrong node hit the login page. You need a shared store — session_db (PostgreSQL) or session_redis — both covered in Part 2. This is the single most common session issue we see in clustered deployments.

Can sticky sessions replace session_db or session_redis?

Sticky sessions (routing a client to the same node via ip_hash or a reverse-proxy cookie) work around the filesystem store being per-machine without introducing a shared store. They avoid new infrastructure, but they trade away the reason you added a second node in the first place: a node failure or a deploy that recycles workers drops every session pinned to it, and load stops being evenly distributed once some clients stick to a busy node. session_db or session_redis remove the pinning requirement entirely, at the cost of one more dependency. For anything beyond a small cluster, prefer the shared store.

Can I read a session file to debug an issue?

Yes: it is plain JSON. You will find uid, db, login, the user’s context, the session_token, debug flags, and device-trace data. Part 1 documents every field. Do not edit files by hand — the session_token binds the content to database state, and a mismatch invalidates the session.


Security

A stolen session_id cookie does grant access while the session is valid — which is why the cookie is HttpOnly (unreadable from JavaScript) and why sessions hard-rotate at login and logout. The mitigation lever is the session_token: changing the victim’s password immediately cuts off the attacker, because the recomputed token no longer matches [4].

Are sessions shared between databases?

No. Each session is bound to exactly one database via its db field, and the session_token is keyed with that database’s own database.secret. On a multi-database server, logging into a second database replaces the session.

Can I embed a logged-in Odoo page inside an iframe on another site?

Not the backend: Odoo sends X-Frame-Options: SAMEORIGIN on backend pages by design, so browsers refuse to render them in a cross-site frame at all. For pages that are meant to be embeddable (portal or website pages), you would still hit the cookie itself: session_id is set without an explicit SameSite attribute [8], which browsers default to Lax — a Lax cookie is not sent on cross-site requests initiated by another site’s iframe. Making a session cookie work cross-site would require deliberately setting SameSite=None; Secure on it, which Odoo does not do for the authentication cookie, since it would weaken CSRF protection for the whole session. If you need cross-site embedding, look at scoping the exposed page to something that does not rely on session_id (a signed one-time token, a public/portal token) rather than trying to relax the cookie.


Integrations and Automation

My integration script authenticates once, then gets “session expired” hours later. What’s happening?

The script’s session is subject to the same inactivity rule as a browser’s: if the script doesn’t touch the session for longer than sessions.max_inactivity_seconds — most likely because it holds the cookie but doesn’t send further authenticated requests during a long processing gap, or because the auto-vacuum cron reaped the file — the next call fails. This is not a JSON-RPC or XML-RPC quirk; it’s the same server-side expiry covered above, just hit by a client that does not retry.

The fix is the same pattern you’d use for any short-lived token: don’t assume one /web/session/authenticate call is good forever. For long-running jobs, either re-authenticate on a session expired response, or check session validity before a batch (e.g. a lightweight authenticated call) and re-authenticate proactively if it fails, rather than discovering it mid-run.


Key Takeaways

Odoo sessions do not expire on a clock — they expire on inactivity, which means an actively used session can live indefinitely, including for integration scripts that need to re-authenticate on long jobs. The inactivity threshold is one system parameter (sessions.max_inactivity_seconds), remembering that cleanup is enforced by a daily cron rather than at request time. An absolute lifetime cap, independent of activity, requires custom code. Force-logout is a password change (one user) or a database.secret rotation (everyone); res.device.log gives visibility into active sessions but not a one-click way to kill one. Restarts never log users out; adding a second node without a shared session store always does, and sticky sessions only paper over that without fully solving it.

Back to the series: Part 1 covers the session internals in depth; Part 2 covers session_db, session_redis, and lifecycle modules.


Sources

[1] odoo/http.py:307-308 (Odoo 19.0, SESSION_LIFETIME, 7 days) and odoo/odoo commit cea9150fb7 (“make session lifetime consistent and configurable”, 16.0)

[2] odoo/http.py:452-461: get_session_max_inactivity()

[3] odoo/http.py:1008-1040: rotate() (soft/hard rotation)

[4] odoo/addons/base/models/res_users.py:851-884: _compute_session_token()

[5] odoo/http.py:968-1006: FilesystemSessionStore

[6] odoo/addons/base/models/ir_http.py:406-410: _gc_sessions() autovacuum cron, ODOO_SKIP_GC_SESSIONS

[7] odoo/addons/base/models/res_device.py:17-38, 84-165: res.device.log fields, update_trace(), revocation bookkeeping

[8] odoo/http.py:1757-1763: Response.set_cookie() — no explicit SameSite passed for session_id, so it falls back to the browser default (Lax)

Ready to get the most out of Odoo?

Whether you are starting a new implementation, upgrading from an older version, or optimizing your current setup — our Odoo-first team is here to help.