-
Notifications
You must be signed in to change notification settings - Fork 4
Troubleshooting
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.
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.
Edit /config/plugins/configurations/TwoFactorAuth/users/<your-guid>.json and clear IpAllowlistCidrs, then restart.
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.
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 = 600That 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.
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.
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_mismatchat 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
SsoLinkexists. 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).
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.
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.
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).
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.
Getting started
Features
Reference
Help