All Server Guides Troubleshooting

Why Audiobookshelf keeps logging you out

Fix the frequent re-login problem in Audiobookshelf v2.26+ caused by JWT session management, reverse proxy timeouts, and lost refresh tokens

Starting with v2.26.0, Audiobookshelf replaced their permanent API tokens with JWT-based authentication. Access tokens now expire after 1 hour, with a 30-day refresh token to silently get new ones. When that refresh fails, you get kicked to the login screen.

Users on the tracking issue report being kicked out multiple times a day. The root cause varies, but there are a few common culprits and workarounds for each.

The error in your server logs usually looks like this:

[TokenManager] Failed to refresh token. Session not found for refresh token: eyJhbGciOiJIUzI1Ni...

That “Session not found” means the server-side session record for your refresh token is gone. The token itself is fine, but the server can’t find a matching session in its database anymore.

Why sessions disappear

There are a few things that can nuke your sessions:

Your config directory is on network storage. This is the big one, especially on Unraid. If your ABS config directory (which contains the SQLite database) lives on a network share or array drive, the database can get corrupted or lose writes. One user fixed it entirely by moving the config to a local SSD using Unraid’s exclusive shares feature.

An admin changed your username. ABS invalidates all JWT sessions for that user when the username changes. This is by design (the old tokens contain the previous username), but it means every device gets logged out.

The server restarted and the database didn’t flush. If Docker kills the container before SQLite finishes writing, your session records can vanish. This is more common with restart: always on underpowered hardware.

Your reverse proxy is cutting the connection. If you’re running a reverse proxy, check our reverse proxy guide for the recommended setup, and see Fix 2 below.

Fix 1: Move your config to local storage

If you’re on Unraid, Synology, or any setup where the ABS config directory might be on a network mount, move it to a local drive. The SQLite database doesn’t play well with network filesystems.

On Unraid specifically, use an exclusive share pointed at a local SSD:

# In your docker-compose.yml, change the config volume from a share to a direct path
volumes:
  - /mnt/cache/appdata/audiobookshelf:/config  # local SSD, not /mnt/user/
  - /mnt/user/audiobooks:/audiobooks
  - /mnt/user/podcasts:/podcasts
  - /mnt/cache/appdata/audiobookshelf/metadata:/metadata

This was the fix for multiple users in the thread. If your config is already on local storage, skip to the next fix.

Fix 2: Check your reverse proxy timeouts

If you’re running ABS behind Nginx Proxy Manager, Traefik, Caddy, or Cloudflare Tunnels, make sure WebSocket connections aren’t getting killed prematurely.

For Nginx Proxy Manager, add this to your Advanced config:

# Keep WebSocket connections alive
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# Don't let the proxy kill idle connections too early
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;

Also make sure WebSockets Support is enabled in NPM’s settings for the proxy host, and turn off Cache Assets and Block Common Exploits. Those have caused issues for some users. This issue was widely reported after the JWT auth change in v2.26.0.

For Traefik, make sure your middleware isn’t stripping WebSocket headers. The connection between the ABS client and server relies on a persistent socket. If that drops, the client thinks the server is gone.

A note on this fix: A maintainer pointed out that proxy timeouts shouldn’t directly cause token refresh failures. But several users reported improvement after these changes, particularly if you’re getting disconnected during playback.

Fix 3: Extend token expiry times

If you’re getting logged out because the default 30-day refresh token isn’t long enough (maybe you only listen on weekends), you can increase it:

# In your docker-compose.yml
environment:
  - REFRESH_TOKEN_EXPIRY=7776000  # 90 days in seconds
  - ACCESS_TOKEN_EXPIRY=3600      # 1 hour (default, leave as-is)

Don’t go overboard with the access token expiry. It’s short on purpose for security. The refresh token is the one that matters for “stay logged in” behavior.

Fix 4: Use VPN instead of exposing ABS publicly

Several users noticed the issue is worse when accessing ABS over the internet through a reverse proxy. If you’re using Tailscale or WireGuard to access your server, you can skip the reverse proxy entirely and connect directly. Fewer moving parts, fewer things to break. We have a Tailscale setup guide if you want to go that route.

A few users reported logouts continued even over Tailscale, so this isn’t always enough on its own.

How SoundLeaf handles this on iPhone

SoundLeaf stores your credentials in the iOS Keychain when you first log in. When the server rejects a refresh token, the app uses those stored credentials to get a fresh session in the background instead of dropping you on the login screen.

In order, on a 401 the app:

  1. Tries to refresh the token.
  2. If the refresh fails, re-authenticates with the stored credentials.
  3. Retries the original request.

You only see the login screen if the server is genuinely unreachable or your password actually changed. If constant re-logins on other clients are wearing you down, give SoundLeaf a try. The getting started guide walks through it.

Update: May 16, 2026, root cause for native iOS clients

@vonrobak posted a forensic analysis on the tracking issue that splits this bug along client lines, and the answer for many iOS users is not the database or network storage. Their server (Docker, Traefik, SQLite on local disk, no restarts) hosts both Firefox and Prologue iOS for the same user. Over five weeks, Firefox stayed logged in on one session; Prologue iOS burned through eight.

They decoded the failed JWTs and compared each token’s iat to the session row’s updatedAt. In every failure, updatedAt was newer than iat, meaning the server had rotated the session after that token was minted, but the client refreshed with the old token anyway. The server’s strict lookup didn’t find it, and the client got logged out.

The sequence behind a lost-rotation failure:

  1. Client refreshes with token A.
  2. Server rotates to token B in the session row, persists, responds with B.
  3. Client never persists B (app suspended mid-write, cellular↔WiFi handoff dropped the response, concurrent refresh).
  4. Hours later, the access token expires; the client refreshes with token A.
  5. Server can’t find a session for token A and returns “Session not found”.

Web browsers are immune because the new refresh token is delivered as Set-Cookie (httpOnly) and browsers commit cookies atomically with no app-lifecycle interruption. Native iOS clients have to read the response body and write to Keychain, which can be interrupted by app suspension or network handoffs. Comments from other users in the thread are consistent with this: the failure rate varies a lot by client (Prologue and Audiobooth hit it often, Audra and the official client less), even on the same server.

The mechanism is visible in server/auth/TokenManager.js. rotateTokensForSession (line 188) overwrites the refresh token and saves immediately:

session.refreshToken = newRefreshToken
session.expiresAt = newExpiresAt
await session.save()

The lookup on the next refresh is strict-equality with no fallback, which is what produces the log line you see (line 302):

Failed to refresh token. Session not found

There is no grace window, no previousRefreshToken column to fall back to, and no idempotency. Any client on a lossy network will eventually hit this.

New workaround: extend ACCESS_TOKEN_EXPIRY

If you’re hitting this on a native iOS client and Fix 1 through Fix 4 didn’t help, vonrobak’s mitigation is to extend ACCESS_TOKEN_EXPIRY so refreshes happen far less often. The default is 1 hour; bumping it to 24 hours cuts the rotation rate by 24×, so the per-day probability of a lost-rotation logout should drop by roughly the same factor.

environment:
  - ACCESS_TOKEN_EXPIRY=86400  # 24 hours, instead of the 3600 default

This is the opposite of the standard “don’t extend access token expiry” advice, and the trade-off is real: if your access token leaks (XSS, intercepted request, exfiltrated header), an attacker has 24 hours instead of 1 to use it. For a self-hosted media server where you control all the clients, that’s usually an acceptable trade-off. For a server exposed to the public internet with multiple users, think harder before flipping it.

This doesn’t fix the underlying bug, just reduces how often you hit it. An upstream fix would need a grace-window column on the session row. PR #5004 by nichwall, open since January 2026, takes exactly that shape: it adds a lastRefreshToken column with a one-minute grace period so a refresh with the previous token still succeeds for a short window after rotation. It’s still in review at the time of writing, but if it lands, the long-ACCESS_TOKEN_EXPIRY workaround can go away.

What this means for the existing fixes above

  • Fix 1 (local storage) still applies to users whose sessions vanish for non-iOS reasons. Several Unraid users in the thread did get well after moving config to a local SSD, so don’t skip it if you’re on a network mount.
  • Fix 2 (reverse proxy) is mostly orthogonal to lost-rotation. The maintainer note about proxy timeouts not directly causing token refresh failures is consistent with the forensic finding.
  • Fix 3 (extend REFRESH_TOKEN_EXPIRY) still helps if you only listen occasionally and the 30-day refresh window expires between sessions.

If you’re on a browser and getting frequent logouts, look at Fixes 1 and 2 first. If you’re on a native iOS client and the server logs show Session not found while the database and proxy look fine, the workaround above is probably your fastest relief until the upstream rotation logic gets a grace window.

Update: May 27, 2026, the grace-window fix shipped

PR #5004 landed. The grace window this post was waiting on shipped in Audiobookshelf v2.35.0 (May 17, 2026), listed in the release notes as “Access token refresh grace period (fixes frequently needing to re-login).” The tracking issue #4630 is now closed. On v2.35.0 and later, a refresh with the previous token still works for a short window after rotation, which is exactly the lost-rotation failure that was hitting native iOS clients. Once you’re on v2.35.0+, you can wind the long-ACCESS_TOKEN_EXPIRY workaround from the May 16 update back down to the 1-hour default if you want; the grace window does the job now.

It wasn’t quite the whole story. Right after v2.35.0, @7enChan found the bug could still fire in a narrower case: if two sessions for the same user call /auth/refresh within the same second, the server hands both the same new refresh token. JWT timestamps are second-resolution and the token carried no per-session value, so two refreshes that land in the same second collide. Log out or refresh one session and the other one dies with the same Session not found line. You need two active sessions for one user to hit it (two devices, or a browser plus the app), so most people won’t, but it’s real and reproducible.

That got its own issue, #5253, and it’s already fixed. PR #5255 (“Add unique UUID to access and refresh tokens”) merged on May 22 and gives every token a unique ID so same-second refreshes can’t produce a duplicate. It isn’t in a tagged release yet (v2.35.0 is still the latest), so it’ll arrive in the next one. If you’re on v2.35.0 with more than one session per user and still catch the occasional logout, that’s the reason, and the fix is queued.

Affected versions

This issue affects Audiobookshelf v2.26.0 and later, any version using the new JWT authentication system. Users on v2.29.0+ have reported it most frequently, possibly due to changes in how sessions are managed.

If you’re still on the legacy token system (pre-2.26.0), you won’t hit this issue, but you also won’t get security updates.