-
Notifications
You must be signed in to change notification settings - Fork 4
Admin Guide
The admin dashboard lives at Dashboard → Plugins → Jellyfin Security. It has five tabs plus an overview, a settings reference, the security score, and encrypted config export.
Per-user 2FA status: TOTP on/off, trusted device count, recovery codes remaining, email address (for OTP), lockout status. Lists all Jellyfin users (not just those with plugin data).
- Set per-user email — for email OTP delivery (admin sets these manually).
- Toggle 2FA on/off — disabling wipes all 2FA state for that user (secret, codes, devices).
Every trusted device across all users with last-used time and expiry. Revoke any to force 2FA on that browser's next login.
Pending TV/native pairing requests — Approve / Deny / QR per request.
Paginated, filterable login-attempt history: successes, failures, lockouts, bypasses, challenge issuances. Backed by a hash chain so silent tampering with audit.json is detectable.
Active brute-force bans with expiry; unban or manually add. See Account Protection.
OIDC/SSO providers. See OIDC / SSO.
- General — plugin toggle, force 2FA for all users, email OTP toggle.
- LAN Bypass — CIDR ranges, Trust X-Forwarded-For, Trusted Proxy CIDRs.
- Security — failed-attempt threshold, lockout duration, audit-log size, exempt-admin-from-lockout, block-empty-password.
- SMTP — host, port, SSL, credentials, from-address (required for email OTP). See Email / SMTP.
- Push Notifications — ntfy URL/topic, Gotify URL/token, admin email addresses.
- Brute-Force / Impossible-Travel — see Account Protection.
- WebAuthn / passkeys — Relying Party ID + allowed origins. See First-Time Setup → Passkeys.
-
Hardening —
RequireTwoFactorToDisable,StepUpLevel, hardened self-service mode,AllowIndefiniteTrust, hide built-in login buttons,DefaultLanguage. - Audit chain — Rebuild audit chain button repairs the hash chain after disk corruption / manual edits (step-up gated).
Overview tab → stacked-area chart of successful / failed / blocked sign-ins.
- Range selector: 1 week / 1 month / 1 year (per-hour, per-day, per-month buckets).
- Sparse-range backfill so a 1-year chart still spans the full range even with one month of data.
- Hover tooltips, dashed gridlines at 25/50/75 %, first/middle/last x-axis date markers.
Raw 130 points, normalized to a 100 ceiling.
| Factor | Points | Checks |
|---|---|---|
| Coverage | 30 | % of users enrolled in 2FA |
| Admin coverage | 20 | All admins specifically have 2FA on |
| Enforcement | 15 |
RequireForAll is on |
| Audit chain | 10 | Hash chain intact |
| IP ban | 8 | Brute-force banning on with a sane threshold |
| Impossible travel | 7 | Functional — GeoIpCityDbPath points to a valid MaxMind file |
| HIBP | 5 | Have-I-Been-Pwned password check on |
| Clean 7-day audit | 5 | No failed admin sign-ins in 7 days |
| Require-to-disable | 8 |
RequireTwoFactorToDisable on |
| Step-up | 7 |
StepUpLevel is Destructive or stronger |
| Webhook | 5 | Push notifications configured |
| Recovery codes | 5 | At least one user generated recovery codes |
Back up or migrate plugin config (settings, OIDC providers, trusted CIDRs, brute-force config) without leaking secrets.
-
Export: Config → Export → enter a passphrase (10+ chars) → download the
.json.encenvelope. - Import: Config → Import → upload → same passphrase → review the change preview → confirm.
Crypto: PBKDF2-SHA256 (600 000 iterations, 16-byte random salt) → AES-256-GCM (12-byte random nonce). AAD binds the plugin + envelope version so an old export can't be replayed against an incompatible schema. Versioned envelope: { "v":1, "salt":..., "nonce":..., "ct":..., "tag":... }.
⚠ No back door. A lost passphrase means the export is unrecoverable — the author cannot decrypt it. Store the passphrase separately from the file.
Ships with 8 languages at full key parity: en, de, es, fr, it, ja, pt, zh. The picker shows each language in its own script. Active language resolves in order: URL ?lang= → per-user preference → localStorage → server-wide DefaultLanguage (Settings → Hardening) → English.
Add a language: copy translations/en.json → translate → drop in translations/<locale>.json. The picker auto-discovers it. PRs welcome.
Getting started
Features
Reference
Help