Skip to content

Admin Guide

ZL154 edited this page Jun 14, 2026 · 1 revision

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.


Dashboard tabs

Users

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).

Trusted Devices

Every trusted device across all users with last-used time and expiry. Revoke any to force 2FA on that browser's next login.

Pairings

Pending TV/native pairing requests — Approve / Deny / QR per request.

Audit Log

Paginated, filterable login-attempt history: successes, failures, lockouts, bypasses, challenge issuances. Backed by a hash chain so silent tampering with audit.json is detectable.

IP Bans

Active brute-force bans with expiry; unban or manually add. See Account Protection.

Sign-in Methods

OIDC/SSO providers. See OIDC / SSO.


Settings reference

  • 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.
  • HardeningRequireTwoFactorToDisable, StepUpLevel, hardened self-service mode, AllowIndefiniteTrust, hide built-in login buttons, DefaultLanguage.
  • Audit chainRebuild audit chain button repairs the hash chain after disk corruption / manual edits (step-up gated).

Auth-activity overview

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.

Security score (12 factors)

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

Encrypted configuration export / import

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.enc envelope.
  • 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.


Internationalization

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.

Clone this wiki locally