Move from Plex to Jellyfin Without Losing Your Rollback Path
Do not migrate by deleting Plex and hoping Jellyfin feels familiar. Mirror the library first, give Jellyfin read-only media access, protect both applications' state, and keep Plex available until the household and the rollback test pass.
Rights and lawful use: This weekend plan covers transferring the organization and playback of media you own, created, or are authorized to use from Plex to Jellyfin. Side-by-side scanning, watched-state tools, metadata handling, and transcoding do not authorize acquisition, DRM bypass, paid sharing, or public distribution; review the access and terms of any third-party migration tool before using it.
Quick Answer
Run Jellyfin beside Plex with separate application data and the same authorized media mounted read-only. Inventory and back up Plex on Friday, scan a small library on Saturday, then test every important user, client, codec, subtitle, audio, and remote path. Do not retire Plex until Jellyfin state can be backed up and restored in a disposable instance and a failed acceptance branch has returned viewers to Plex.
Who this is for: This is for a Plex operator evaluating Jellyfin who needs a reversible cutover across existing media paths, users, watched state, remote access, hardware acceleration, and household clients. It assumes you can maintain separate Plex and Jellyfin configuration, cache, metadata, transcode, and database paths while both servers read the same authorized library during the pilot.
What Moves Cleanly, and What Does Not
The media files are the shared input, not the application database. Plex and Jellyfin should point at the same stable folder structure during the pilot, but each server needs its own configuration, database, cache, metadata, logs, and transcode directory. Never place one server's internal state inside the other server's app-data tree.
Jellyfin's migration documentation, rechecked on August 24, 2026, says its internal databases cannot be copied or adjusted easily. It links a third-party application programming interface (API) tool for selected Plex user and watched-status transfers. That is not a supported promise of complete Plex database conversion. Review the tool's source, permissions, release activity, and rollback behavior before giving it tokens or household viewing data.
| State | Migration approach | Cutover gate |
|---|---|---|
| Media files | Keep the existing names and paths; mount read-only into Jellyfin first. | Representative items scan and play without source-file changes. |
| Users and permissions | Create least-privilege Jellyfin users manually or evaluate a narrowly scoped third-party API tool. | Every user sees only intended libraries and has no administrator rights. |
| Watched state | Treat third-party transfer as optional; retain Plex as the authoritative rollback copy. | Sample completed, in-progress, and unwatched items match the written expectation. |
| Posters, collections, and playlists | Rescan, use reviewed local metadata, or rebuild deliberately. | Household-critical items are listed and checked; no claim of full parity is assumed. |
| Remote access | Build a separate Jellyfin address and access path after local playback works. | An off-LAN normal user can connect without administrator privileges or unrelated management-service exposure. |
| Application database | Do not copy Plex database files into Jellyfin. | Each application backs up and restores using its own documented method. |
Friday: Inventory the Working Plex System
Record the facts needed to reproduce the current service before changing it: host and operating system, Plex package or container image, media-server version, storage mounts, user and group IDs, library paths, remote hostname, certificate owner, hardware devices, clients, users, and backup location. Run the commands that match the deployment. The block below reads state; it does not change Plex or the library.
date -u '+%Y-%m-%dT%H:%M:%SZ'
uname -a
id
findmnt -T /srv/media
stat -c 'path=%n uid=%u gid=%g mode=%a type=%F' /srv/media
docker version --format 'docker_client={{.Client.Version}} docker_server={{.Server.Version}}'
docker compose version --short
docker inspect plex --format 'image={{.Config.Image}} id={{.Id}}'
docker inspect plex --format '{{range .Mounts}}{{println .Source "->" .Destination "rw=" .RW}}{{end}}'
docker image inspect "$(docker inspect plex --format '{{.Image}}')" --format '{{json .RepoDigests}}'
Illustrative output format only; not performed by TechGeeks. Values below are synthetic:
2026-08-24T14:30:00Z
Linux media-canary 6.8.0-example x86_64 GNU/Linux
uid=1000(media) gid=1000(media) groups=1000(media),44(video)
TARGET SOURCE FSTYPE OPTIONS
/srv/media /dev/mapper/media ext4 ro,relatime
docker_client=28.0.0-example docker_server=28.0.0-example
2.0.0-example
image=plexinc/pms-docker:<RECORDED_TAG> id=<CONTAINER_ID>
/srv/plex/config -> /config rw= true
/srv/media -> /media rw= false
["plexinc/pms-docker@sha256:<RECORDED_DIGEST>"]
The output format is the example, not the values. Save the real command, working directory, UTC time, exit code, and unedited output under the planned lab artifact. Redact hostnames, addresses, usernames, media names, and tokens from any publishable copy.
Friday: Create the Rollback Point
Back up the Plex Media Server data directory and any platform-specific settings using Plex's current backup guidance. A Docker example is below. Replace every marker, confirm the source directory from the actual container mount, and keep the archive outside the live Plex data directory. Stop the container so the copied database state is consistent.
export BACKUP_ROOT=/srv/backups/plex-migration
export PLEX_CONFIG=/srv/plex/config
export RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
sudo install -d -m 0750 "$BACKUP_ROOT/$RUN_ID"
docker stop plex
sudo tar --acls --xattrs --numeric-owner \
-C "$PLEX_CONFIG" \
-czf "$BACKUP_ROOT/$RUN_ID/plex-config.tar.gz" .
sudo sha256sum "$BACKUP_ROOT/$RUN_ID/plex-config.tar.gz" \
| sudo tee "$BACKUP_ROOT/$RUN_ID/SHA256SUMS"
docker start plex
sudo sha256sum -c "$BACKUP_ROOT/$RUN_ID/SHA256SUMS"
Example expected output; not observed:
/srv/backups/plex-migration/<RUN_ID>/plex-config.tar.gz: OK
A checksum proves that the archive can be read unchanged; it does not prove that Plex can restore from it. Keep the current image digest, Compose file, environment-variable names, mount definitions, and a separate administrator recovery path with the backup. Do not delete or overwrite the existing Plex state during this weekend.
Saturday: Start a Read-Only Jellyfin Pilot
This Compose file is a reference configuration, not a TechGeeks-deployed result. Replace every typed marker, store secrets outside the file, and record the image digest after pulling. The long volume syntax makes the media permission visible in review. Remove the graphics-device mapping if the host does not have /dev/dri; its presence alone does not prove hardware acceleration.
name: jellyfin-migration-pilot
services:
jellyfin:
image: ${JELLYFIN_IMAGE}
container_name: jellyfin-migration
user: "${PUID}:${PGID}"
group_add:
- "${JELLYFIN_RENDER_GID}"
ports:
- "${JELLYFIN_BIND_IP}:8096:8096/tcp"
volumes:
- type: bind
source: ${JELLYFIN_CONFIG}
target: /config
- type: bind
source: ${JELLYFIN_CACHE}
target: /cache
- type: bind
source: ${MEDIA_PATH}
target: /media
read_only: true
devices:
- /dev/dri/renderD128:/dev/dri/renderD128
restart: unless-stopped
Use a sibling .env file with values from the inventory. Pin JELLYFIN_IMAGE to the digest actually pulled for the lab rather than copying the synthetic digest shown here.
JELLYFIN_IMAGE=jellyfin/jellyfin@sha256:<RECORDED_DIGEST>
JELLYFIN_BIND_IP=<PRIVATE_LAN_IP>
JELLYFIN_CONFIG=/srv/jellyfin/config
JELLYFIN_CACHE=/srv/jellyfin/cache
MEDIA_PATH=/srv/media
PUID=<MEDIA_UID>
PGID=<MEDIA_GID>
JELLYFIN_RENDER_GID=<HOST_RENDER_DEVICE_GID>
Set JELLYFIN_RENDER_GID to the numeric group that owns the selected host render device; determine it from stat -c '%g' /dev/dri/renderD128. Some systems use a group named render, video, or input. Remove group_add and devices when hardware acceleration is not part of the pilot. A mapped device and supplementary group make access possible but do not prove acceleration.
Create the writable Jellyfin paths, render the final configuration, pull the image, record the digest, and start only the pilot service:
set -a
. ./.env
set +a
sudo install -d -o "${PUID}" -g "${PGID}" -m 0750 \
"$JELLYFIN_CONFIG" "$JELLYFIN_CACHE"
docker compose config
docker compose pull jellyfin
docker image inspect "$(docker compose images -q jellyfin)" \
--format '{{json .RepoDigests}}'
docker compose up -d jellyfin
docker compose ps jellyfin
Before adding a library, verify the mount mode and run a safe negative write test. The touch command must fail; if it succeeds, stop the pilot and correct the mount before scanning.
docker inspect jellyfin-migration \
--format '{{range .Mounts}}{{println .Destination .RW}}{{end}}'
READ_ONLY_GATE=pass
if docker exec jellyfin-migration sh -c \
'touch /media/.techgeeks-read-only-check'; then
printf 'read_only_gate=FAIL (write succeeded)\n' >&2
docker exec jellyfin-migration rm -f /media/.techgeeks-read-only-check
READ_ONLY_GATE=fail
else
printf 'expected_touch_exit_code=%s\n' "$?"
fi
if [ "$READ_ONLY_GATE" != pass ]; then
docker compose stop jellyfin
exit 1
fi
curl --fail --silent --show-error \
"http://${JELLYFIN_BIND_IP}:8096/health"
Example expected output; not observed:
/config true
/cache true
/media false
touch: /media/.techgeeks-read-only-check: Read-only file system
expected_touch_exit_code=1
Healthy
The negative command is expected to return a nonzero exit code. If the write unexpectedly succeeds, the block attempts to remove its marker, stops the pilot, and exits nonzero before the health check. Capture every exit code and output rather than editing the transcript into a pass. If the gate fails, inspect the host media path for the marker and correct the mount before scanning. Add one small library only after the gate passes.
Metadata, Users, and Watched State
Keep the source library unchanged while learning how Jellyfin matches it. Jellyfin can read and write local NFO files, and its current documentation says local NFO metadata takes priority over remote providers. A read-only mount prevents Jellyfin from writing new NFO or artwork beside the media. If the library already contains NFO files, inspect a synthetic or authorized sample before scanning because those files can affect matches and selected user data.
- Create a separate non-admin test user for each policy pattern: adult, child, remote, or restricted library.
- Record library visibility, downloads, remote access, transcoding rights, and password-recovery ownership.
- Treat Plex users, Plex Home relationships, collections, playlists, posters, and client preferences as manual acceptance items unless a reviewed tool explicitly handles them.
- If a watched-state tool is evaluated, use a dedicated least-privilege token, export before and after samples, revoke the token, and keep Plex authoritative until the comparison is accepted.
- Never put server tokens, household email addresses, library names, or viewing history into a public screenshot or command transcript.
Security, Privacy, Legal, and Recovery Boundaries
- Security: use separate normal-user accounts because the media application may serve playback and dashboard routes on one endpoint; keep unrelated host-management services off the playback path, run the pilot with the recorded user/group identity, patch both servers during overlap, and revoke temporary migration tokens.
- Privacy: media names, posters, users, watch history, addresses, logs, and tool exports can reveal household behavior. Use synthetic labels for evidence, minimize retention, and review every redaction at original resolution.
- Legal: migrate only authorized media and metadata. A copied library, NFO file, subtitle, or watched-state record does not create new content or distribution rights.
- Recovery: keep Plex state, media paths, and the prior remote route intact; back up Jellyfin separately; and maintain a local administrator path that does not depend on public DNS, a proxy, or a third-party migration service.
Sunday: Run the Real-Client Acceptance Matrix
Use lawful test media that represents the actual failure classes, not private title names. Record container, video codec, bit depth, high dynamic range (HDR) format, audio codec and channels, subtitle type, bitrate, duration, and a checksum. This command records media characteristics without changing the file:
ffprobe -v error \
-show_entries format=filename,format_name,duration,bit_rate \
-show_entries stream=index,codec_type,codec_name,profile,pix_fmt,width,height,channels \
-of json \
/srv/test-media/<AUTHORIZED_SAMPLE>
Illustrative output shape only; not performed. Values and filename are synthetic:
{
"streams": [
{"index": 0, "codec_type": "video", "codec_name": "hevc", "profile": "Main 10", "pix_fmt": "yuv420p10le", "width": 3840, "height": 2160},
{"index": 1, "codec_type": "audio", "codec_name": "eac3", "channels": 6},
{"index": 2, "codec_type": "subtitle", "codec_name": "hdmv_pgs_subtitle"}
],
"format": {"filename": "/srv/test-media/synthetic-hdr-pgs.mkv", "format_name": "matroska,webm", "duration": "120.000000", "bit_rate": "25000000"}
}
| Client and network | Sample | Plex baseline | Jellyfin result | Account steps | Pass condition | Artifact ID |
|---|---|---|---|---|---|---|
| Main TV on LAN | Ordinary H.264 plus text subtitle | Record actual mode | Planned | Record sign-in and recovery | Starts, seeks, subtitles, audio, and resume work | Planned |
| Streaming box on LAN | HEVC/HDR plus image subtitle | Record actual mode | Planned | Record app availability | Expected HDR or documented tone map; no unexplained transcode | Planned |
| Browser on LAN | Unsupported codec trigger | Record reason | Planned | Normal user only | Dashboard and host telemetry agree on the conversion path | Planned |
| Phone off LAN | Capped remote stream | Record direct or relayed path | Planned | Record VPN, proxy, or account path | Works on a separate network without administrator privileges or unrelated management exposure | Planned |
For every row, capture server and client versions, connection path, playback decision, transcode reason, startup and seek outcome, delivered bitrate, subtitle and audio behavior, and central processing unit (CPU) or graphics processing unit (GPU) telemetry. A dashboard label alone does not prove image quality or hardware use. Corroborate it with the server log and process or GPU telemetry.
Validation and Cutover Gate
- Every household-critical client passes the agreed sample set locally.
- Every remote user can sign in from a separate network using the selected private or hardened path.
- Library visibility and least-privilege permissions match the written matrix.
- Metadata mismatches, collections, playlists, posters, and watched-state exceptions have owners and accepted outcomes.
- Expected Direct Play, Direct Stream, and transcode decisions are explained; no purchase is justified by an unexplained dashboard state.
- Jellyfin configuration and database state have a versioned backup outside the live config path.
- A disposable restore and a return-to-Plex rehearsal have passed with reviewed artifacts.
Backup, Restore, and Rollback
Jellyfin's current 10.11 backup documentation describes a built-in online backup under Dashboard > Backups. The database is always included; metadata, subtitles, and trickplay are selectable. The documentation recommends low activity with no scan running and warns that the 5 GB free-space precheck may not cover optional data. It also documents restore from the web interface or with --restore-archive PATH_TO_BACKUP_ZIP. Jellyfin has no downgrade mechanism, so record the running image digest and restore matching state rather than assuming an older image can open a migrated database.
The publication lab must create a built-in backup, copy the archive outside the live config tree, checksum it, and restore it into a disposable instance with no access to production media. The restore passes only when the expected synthetic library, users, permissions, and settings return and the restored instance starts with the recorded version.
- Declare the acceptance branch failed and stop adding users or state to Jellyfin.
- Remove the Jellyfin public proxy, DNS, or VPN route if one was created; keep a local operator route long enough to export logs.
- Revoke migration-tool and temporary test tokens, then stop the Jellyfin container.
- Return each test client to the unchanged Plex server and verify one local and one off-LAN playback path.
- Confirm the Plex app-data backup checksum and retain it. Restore Plex only if the still-running Plex state is actually damaged.
- Keep the read-only media library untouched. Diagnose application state separately from media recovery.
Rollback is not complete when the Plex sign-in page opens. The planned rehearsal must prove a normal user can browse the intended library, resume a known synthetic item, and play locally. If remote access is part of the household requirement, that path must also return without granting the viewer administrator privileges or exposing unrelated management services.
Troubleshooting
| Symptom | Likely boundary | First check | Recovery move |
|---|---|---|---|
| Library path is empty | Host mount, container source path, user/group ID, or permissions | Compare findmnt, stat, and docker inspect with the inventory | Stop Jellyfin; correct the mount without changing Plex |
| Unexpected transcode | Client codec, container, audio, subtitle, bitrate, or GPU mapping | Read the playback reason and compare host telemetry | Keep Plex available; change one variable and repeat the same sample |
| Wrong match or poster | Naming, existing NFO, provider priority, or manual edits | Inspect one item and its local metadata | Remove the pilot library and rescan after correcting the policy |
| Watched state is incomplete | Third-party transfer scope or user mapping | Compare a written sample set, not total counts alone | Revoke the tool token and retain Plex as authoritative |
| Remote Jellyfin fails | DNS, certificate, VPN/proxy, firewall, NAT, or per-user permission | Prove local playback first, then trace the selected access path | Remove the new route and return the client to Plex |
| Restore will not start | Version mismatch, incomplete archive, ownership, or live database | Compare digest, checksum, paths, and logs in the disposable target | Discard the disposable target; do not alter either production app |
Planned Real-Client and Lab Evidence
Evidence status: planned and publication-blocking. No TechGeeks Plex-to-Jellyfin migration, scan, playback comparison, backup restore, screenshot, timing, or rollback rehearsal was performed for this revision. Store a future run under artifacts/labs/plex-to-jellyfin-migration-weekend-guide/YYYY-MM-DD/ and assign immutable IDs to every artifact.
- Record environment, topology, server and client versions, image digests, CPU, GPU, driver, RAM, storage, network, configuration hashes, and safe test-media traits.
- Capture the exact inventory and backup commands with UTC time, working directory, exit code, complete output, and redaction log.
- Prove the Jellyfin media mount is read-only with
docker inspectand the safe failed-write test. - Time a small and full scan; count matches needing repair without exposing private titles.
- Run the LAN and off-LAN client matrix with one browser, one television or streaming device, and one mobile client.
- Capture Direct Play, Direct Stream, and transcode decisions plus host process or GPU telemetry for the same sample and time window.
- Test normal, restricted, and remote users; document account recovery and revocation without administrator access.
- Exercise one safe negative path, return clients to Plex, and record the rollback outcome.
- Create a Jellyfin built-in backup and restore it into a disposable matching-version instance.
- Capture redacted screenshots of parallel library state, backup archive state, restore result, and the completed client matrix at documented viewports.
- Have a second reviewer inspect every redacted artifact at original resolution before publication.
What This Evidence Does Not Prove
- A read-only parallel scan does not prove every title, edition, extra, collection, playlist, poster, or NFO will match.
- A watched-state transfer does not prove complete user, permission, metadata, or database migration.
- One client matrix does not establish support for untested firmware, apps, codecs, subtitle formats, audio chains, or remote networks.
- A dashboard playback label does not prove viewer-perceived quality or that every decode, filter, tone-map, and encode stage used hardware.
- A successful backup checksum does not prove restore; a disposable restore does not prove a cross-platform or downgrade path.
- This documentation-backed revision reports no TechGeeks scan time, migration duration, resource measurement, playback-quality result, or rollback result.
Useful Gear And Buyer Notes
Buy only after the client matrix names a bottleneck. An external disk may protect application state; a compatible client may remove a transcode; a graphics processor matters only when required conversions exceed measured capacity; and network gear matters only when the tested network path is the constraint. The search links remain category references because models, bundles, sellers, and prices change.
Affiliate disclosure: As an Amazon Associate, TechGeeks may earn from qualifying purchases. The product links below are buying references, not a requirement to buy a specific brand or seller. Verify compatibility, seller quality, warranty, and current specs before ordering.
- Amazon search: external hard drive backup 12TB
- Amazon search: SATA SSD Docker appdata
- Amazon search: Intel N100 mini PC Jellyfin
- Amazon search: 2.5GbE unmanaged switch
- Amazon search: USB 2.5GbE adapter Linux
Related TechGeeks Resources
- Plex vs Jellyfin Remote Streaming owns the account, client, and access decision.
- Jellyfin Remote Access with Tailscale is the private-access implementation path; it is staged in this campaign and must be live before this link is published.
- Plex Homelab Architecture covers storage, GPU, and library design.
- Media Server Storage Design covers NAS mounts, permissions, and cache placement.
- Monitoring and Health Checks covers ongoing server and automation visibility.
Series Navigation
References
- Jellyfin: Migrating
- Jellyfin: Backup and Restore
- Jellyfin: Container Installation
- Jellyfin: Intel GPU Container Access
- Jellyfin: Libraries
- Jellyfin: Local NFO Metadata
- Plex: Backing Up Plex Media Server Data
- Plex: Naming and Organizing Movie Files
Community discussions informed the migration questions but are not product authority: Plex versus Jellyfin discussion and Jellyfin client-experience discussion.
Final Thought
A migration is complete only when the new service works for normal viewers and the old service remains a proven exit. Keep Plex intact, make Jellyfin earn the cutover through the client matrix, then preserve both the Jellyfin restore artifact and the Plex rollback point.


