Skip to content

Troubleshooting

ZL154 edited this page Jun 17, 2026 · 3 revisions

Troubleshooting


🆘 Recovery — locked out

Lost authenticator, but you have recovery codes

Sign in via /TwoFactorAuth/Login. In the code field, enter one of your recovery codes (format XXXXX-XXXXX). Click Use a recovery code instead if the authenticator field is showing.

Lost authenticator AND recovery codes (admin reset)

SSH into the server and edit the user data file:

/config/plugins/configurations/TwoFactorAuth/users/{userId}.json

Set:

"TotpEnabled": false,
"TotpVerified": false,
"EncryptedTotpSecret": null,
"RecoveryCodes": [],
"TrustedDevices": []

Restart Jellyfin. The user can log in normally and re-enroll.

Locked out by an IP allowlist typo

Edit /config/plugins/configurations/TwoFactorAuth/users/<your-guid>.json and clear IpAllowlistCidrs, then restart.


Plugin is breaking your server

Disable enforcement without uninstalling — edit:

/config/plugins/configurations/Jellyfin.Plugin.TwoFactorAuth.xml

Set:

<Enabled>false</Enabled>

Restart Jellyfin. All 2FA enforcement turns off and users can log in normally.


Behind SWAG / fail2ban: other services go offline after a 2FA login

Symptoms: Jellyfin works on the LAN; external access fails with ERR_CONNECTION_REFUSED; other apps behind the same proxy also break; brief recovery every ~10–15 min, then it fails again.

Why: when 2FA enforcement is on, a single legitimate login produces ~15 HTTP 401s in a few seconds (the plugin 401s every post-login API call until 2FA completes). SWAG's default nginx-unauthorized fail2ban jail bans an IP after 5 401s in 10 minutes — so one login trips it. What breaks depends on which IP fail2ban sees: Cloudflare's edge IP (all external traffic dies), the Docker bridge gateway (inter-container traffic dies), or the real client IP (only that user locked out).

Fix — drop into /config/fail2ban/jail.d/jellyfin.local:

[nginx-unauthorized]
maxretry = 30
findtime = 600

That raises the threshold to 30 401s in 10 min (~2× headroom over a normal login) while still catching real brute-force.

Scale by user count (behind a CDN, all users share one source IP from fail2ban's view):

Users Recommended maxretry
1 (solo) 30
2–3 50
4–6 100
10+ 150 or enabled = false

Restart SWAG (docker restart swag) after the change.

Or disable the jail ([nginx-unauthorized]enabled = false) — you lose generic 401-burst protection for all apps behind SWAG, but the other SWAG jails still cover common brute-force vectors. Tracking: issue #36.


LAN bypass stopped working

If you pasted a broad RFC1918 range (e.g. 10.0.0.0/8) into Trusted Proxy CIDRs, the SEC-H3 guard can't distinguish a stale-XFF proxy from a direct LAN client and refuses LAN bypass for everyone. List your proxy's actual address(es) instead. The refusal is logged at Information level on first hit per peer IP — check the Jellyfin log.


OIDC sign-in bounces back to the login page

v2.5.11: a refused sign-in now shows a clear reason on the login page (a red toast) instead of silently bouncing — "No matching Jellyfin account…", "…isn't in a group allowed…", "requires MFA…", etc. Read the toast first; the cases below explain each.

  • redirect_uri_mismatch at the IdP — the registered redirect URI doesn't match the plugin's. The provider list shows the exact URL; the <slug> comes from the Display name. See OIDC / SSO → step 3.
  • "No matching Jellyfin account" — no SSO link yet and the IdP email isn't on a Jellyfin user. Set the user's email in the Users tab (or let it auto-fill — see OIDC / SSO), or sign in with a password once and link the provider from the Setup page.
  • Signs you into the wrong / always-the-same user, or "email is on more than one account" — the email is configured on more than one Jellyfin user, or a stray SsoLink exists. Make the email unique in the Users tab. (v2.5.11 also ignores stale email records left by deleted accounts, which previously caused a phantom duplicate that blocked the surviving user.)
  • Private/LAN IdP rejected — enable Allow private / VPN / LAN endpoints on that provider (see OIDC / SSO).

Google sign-in fails on the Android app ("disallowed_useragent")

v2.5.12. Google blocks its OAuth consent screen inside an app's embedded WebView ("Use secure browsers" policy → Error 403: disallowed_useragent). The plugin tries to hand the sign-in off to your phone's real browser, but some app builds don't expose a working external-browser bridge, so the consent can end up loading inside the WebView.

Workaround that always works: when you tap the SSO button in the app, a small dialog appears with "Open sign-in in browser" and "Copy sign-in link." Tap Copy sign-in link, paste it into Chrome (or your default browser), finish the sign-in there, then switch back to the Jellyfin app — it completes automatically.

This only affects the native app's in-app browser; desktop and mobile web browsers use the normal redirect and aren't affected.


Sessions get 403'd after a container restart

Fixed in v2.5.7 (#52) — verified-token hashes now persist to verified_tokens.json and are reloaded on startup. After the first successful login following an upgrade, the log shows [2FA] Loaded N verified-token hashes from …. If you still see it on older versions, upgrade.


Web UI renders as random characters / mojibake

Fixed in v1.4.2 — the index-injection middleware was mis-handling gzipped /web/index.html. Upgrade. If you're on a CDN, also ensure inject.js isn't pinned by cache (it's served Cache-Control: no-store).


Native app (Tizen / Smart TV) can't sign in behind a proxy

Fixed in v1.4.1 — the enforcement middleware now reads SessionInfo.DeviceId from the auth response when request headers don't carry one, and normalizes Tizen webview device IDs across app restarts. Upgrade; no re-pair needed.


Still stuck? Open an issue with your Jellyfin version, plugin version, deployment (Docker / bare metal), and reverse-proxy setup.

Clone this wiki locally