Building a Media Server with Jellyfin: The Complete Guide
A media server is one of those home lab projects that the whole household actually benefits from. Instead of files scattered across hard drives and USB sticks, you get a Netflix-like interface for your entire media collection — movies, TV shows, music, photos — streamable to any device in the house. And unlike cloud streaming services, you own the content and the platform.
Jellyfin is the leading free, open-source media server. No accounts, no subscriptions, no telemetry, no artificial feature restrictions. It's a fully community-driven fork of Emby, and it's become the default recommendation in the self-hosting community for good reason.
This guide covers everything from installation to hardware transcoding, client apps, remote access, and honest comparisons with the alternatives.
Why Jellyfin Over Plex or Emby?
Before we set anything up, let's talk about the landscape. There are three major media server platforms, and they each have distinct trade-offs.
Jellyfin
- Cost: Completely free. No premium tier, no paid features.
- Open source: MIT licensed. You can inspect, modify, and contribute to the code.
- No account required: Jellyfin doesn't phone home. There's no central authentication server, no data collection, no telemetry.
- Hardware transcoding: Free and built-in. No paywall.
- Client apps: Official apps for Android, iOS, Android TV, Roku, and web. Quality varies — the Android app is excellent, the iOS app is functional but less polished.
- Plugins: Community plugin system for additional metadata providers, themes, and integrations.
Plex
- Cost: Free tier with ads and limitations. Plex Pass ($5/month or $120 lifetime) unlocks hardware transcoding, mobile sync, and other features.
- Closed source: Proprietary. You run their software, they control the roadmap.
- Account required: Plex requires a plex.tv account. Authentication goes through their servers. If plex.tv goes down, you might not be able to access your own media.
- Best client apps: Plex has the most polished, widely available client apps. Smart TVs, game consoles, everything.
- Remote access: Plex's relay system makes remote access easier to set up (at the cost of routing through their servers).
Emby
- Cost: Free tier with restrictions. Emby Premiere ($5/month or $119 lifetime) unlocks hardware transcoding, live TV, and mobile sync.
- Source-available: Code is visible but not open source. The license restricts modification and redistribution.
- Middle ground: Better mobile apps than Jellyfin, less ecosystem lock-in than Plex.
The bottom line: If you value open source and privacy, Jellyfin is the clear choice. If you need the best possible client experience on every device (especially smart TVs and game consoles), Plex is hard to beat. If you're undecided, start with Jellyfin — it's free, and you can always switch later.
Installation
Docker (Recommended)
Docker is the cleanest way to run Jellyfin. One container, easy updates, no dependency conflicts.
# docker-compose.yml
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
ports:
- "8096:8096" # Web UI (HTTP)
- "8920:8920" # Web UI (HTTPS, optional)
- "7359:7359/udp" # Client discovery on local network
- "1900:1900/udp" # DLNA (optional)
environment:
- JELLYFIN_PublishedServerUrl=http://192.168.1.50
volumes:
- ./config:/config
- ./cache:/cache
- /mnt/media/movies:/data/movies
- /mnt/media/shows:/data/shows
- /mnt/media/music:/data/music
devices:
- /dev/dri:/dev/dri # Intel GPU for hardware transcoding
docker compose up -d
Open http://192.168.1.50:8096 in your browser. The setup wizard walks you through creating an admin account, selecting your language, and adding your first library.
Bare Metal (Debian/Ubuntu)
If you prefer running Jellyfin directly on the OS:
# Add the Jellyfin repository
sudo apt install curl gnupg
curl -fsSL https://repo.jellyfin.org/install-debuntu.sh | sudo bash
# Install Jellyfin
sudo apt install jellyfin
# Start and enable
sudo systemctl enable --now jellyfin
# Check status
sudo systemctl status jellyfin
Jellyfin runs on port 8096 by default. The service runs as the jellyfin user, so make sure your media directories are readable by that user.
LXC Container (Proxmox)
If you're running Proxmox, an unprivileged LXC container works well for Jellyfin. Create an Ubuntu 24.04 container, then follow the bare metal steps above. For hardware transcoding in LXC, you'll need to pass through the GPU device — see the transcoding section below.
Organizing Your Media Library
Jellyfin (and its metadata scrapers) expects a specific directory structure. Get this right from the start and you'll avoid 90% of metadata issues.
Movies
/data/movies/
├── Blade Runner 2049 (2017)/
│ └── Blade Runner 2049 (2017).mkv
├── The Matrix (1999)/
│ ├── The Matrix (1999).mkv
│ └── The Matrix (1999).srt # External subtitles
├── Dune Part Two (2024)/
│ └── Dune.Part.Two.2024.2160p.mkv
└── My Neighbor Totoro (1988)/
└── My Neighbor Totoro (1988).mkv
The key rules:
- One folder per movie, named
Movie Title (Year) - The video file should match the folder name
- External subtitles go in the same folder as the video, with the same base name
- Subtitle language codes:
Movie.en.srt,Movie.es.srt,Movie.ja.srt
TV Shows
/data/shows/
├── Breaking Bad/
│ ├── Season 01/
│ │ ├── Breaking Bad S01E01.mkv
│ │ ├── Breaking Bad S01E02.mkv
│ │ └── ...
│ ├── Season 02/
│ │ ├── Breaking Bad S02E01.mkv
│ │ └── ...
│ └── Season 05/
│ └── ...
├── Severance/
│ ├── Season 01/
│ │ ├── Severance S01E01.mkv
│ │ └── ...
│ └── Season 02/
│ └── ...
└── Shogun (2024)/
└── Season 01/
└── ...
The S01E01 naming convention is essential. Jellyfin uses it to match episodes to metadata. Absolute episode numbering (E001, E002) works for anime.
Music
/data/music/
├── Pink Floyd/
│ ├── The Dark Side of the Moon (1973)/
│ │ ├── 01 - Speak to Me.flac
│ │ ├── 02 - Breathe.flac
│ │ └── ...
│ └── Wish You Were Here (1975)/
│ └── ...
└── Radiohead/
└── OK Computer (1997)/
└── ...
For music, proper ID3/Vorbis tags matter more than file names. Jellyfin reads embedded tags for artist, album, and track metadata.
Adding Libraries
In the Jellyfin dashboard:
- Go to Dashboard > Libraries
- Click Add Media Library
- Choose the content type (Movies, Shows, Music, etc.)
- Select the folder path (
/data/movies,/data/shows, etc.) - Choose your metadata providers:
- Movies: TheMovieDb is the default and works well
- TV Shows: TheMovieDb or TheTVDB
- Music: MusicBrainz
Jellyfin scans the library and downloads metadata, artwork, and descriptions automatically. The initial scan can take a while for large libraries — let it run in the background.
Metadata Tips
- Movie name conflicts: If Jellyfin picks the wrong movie, create a
movie.nfofile in the movie folder with the correct TheMovieDb URL. - Missing metadata: Check that your folder and file naming matches the conventions above. Nine times out of ten, it's a naming issue.
- Manual identification: Right-click any item in the library and choose "Identify" to manually search and select the correct match.
- Refresh metadata: After fixing naming issues, right-click the library and choose "Refresh All Metadata."
Hardware Transcoding
Transcoding is the process of converting video from one format to another on the fly. When a client can't play the original format (wrong codec, resolution too high, or bandwidth too low), Jellyfin transcodes it in real time.
Software transcoding (CPU-only) works but hammers your processor. A 4K HEVC to 1080p H.264 transcode can peg an 8-core CPU at 100%. Hardware transcoding offloads this to a GPU, dropping CPU usage to near zero.
Intel Quick Sync (QSV)
Intel's integrated GPUs (6th gen Skylake and newer) include Quick Sync Video, which is excellent for transcoding. This is the most popular choice in home labs because most Intel CPUs have it built in — no discrete GPU needed.
# Docker Compose — pass through the Intel GPU
services:
jellyfin:
image: jellyfin/jellyfin:latest
devices:
- /dev/dri:/dev/dri
group_add:
- "105" # render group GID — check with: getent group render
# Verify Intel GPU is available on the host
ls /dev/dri/
# Should show: card0 renderD128
# Check the render group GID
getent group render
# render:x:105:
In Jellyfin settings:
- Go to Dashboard > Playback > Transcoding
- Set Hardware Acceleration to Intel QuickSync (QSV)
- Enable the codecs your GPU supports (H.264, HEVC, VP9, AV1 depending on generation)
- Enable Hardware Decoding and Hardware Encoding
- Enable Tone Mapping for HDR to SDR conversion if you have 10th gen or newer
Quick Sync generation capabilities:
| Intel Gen | H.264 | HEVC | VP9 | AV1 | HDR Tone Mapping |
|---|---|---|---|---|---|
| 6th (Skylake) | Decode + Encode | Decode only | No | No | No |
| 7th-9th | Decode + Encode | Decode + Encode | Decode only | No | No |
| 10th-11th | Decode + Encode | Decode + Encode | Decode + Encode | No | Yes |
| 12th+ | Decode + Encode | Decode + Encode | Decode + Encode | Decode + Encode | Yes |
The Intel N100 (common in mini PCs like the Beelink EQ12) is a 12th-gen chip with full AV1 support and HDR tone mapping, all at 6W idle. It's arguably the best transcoding CPU per watt for a home lab.
NVIDIA NVENC
If you have a discrete NVIDIA GPU (GTX 1050 or newer), NVENC is powerful and well-supported:
# Docker Compose — NVIDIA GPU passthrough
services:
jellyfin:
image: jellyfin/jellyfin:latest
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# Install NVIDIA Container Toolkit first
# See: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html
# Verify GPU is accessible in container
docker exec jellyfin nvidia-smi
In Jellyfin settings, set Hardware Acceleration to NVIDIA NVENC.
NVIDIA GPUs are overkill for most home labs but useful if you already have one from a gaming PC. They handle more simultaneous transcodes than Intel QSV.
AMD AMF / VA-API
AMD GPUs and APUs support VA-API for transcoding. Support in Jellyfin is functional but less mature than Intel QSV or NVIDIA NVENC. Pass through /dev/dri the same way as Intel, and select Video Acceleration API (VA-API) in Jellyfin settings.
Avoiding Transcoding Entirely
The best transcode is no transcode. If all your clients can direct-play your media, you don't need transcoding at all. To maximize direct play:
- Use H.264 for maximum compatibility (every device supports it)
- Use AAC audio (or keep a secondary AAC audio track alongside lossless)
- Keep resolution at 1080p unless all your clients support 4K
- Use MP4 containers instead of MKV for broader client support
Most modern clients (Android TV, Apple TV, Fire Stick 4K, web browser) handle H.264 1080p in an MKV container without transcoding. HEVC/H.265 and 4K are where transcoding becomes necessary for older or less capable clients.
Client Apps
Jellyfin has clients for most platforms. Quality varies:
Excellent
- Jellyfin Web: The built-in web client. Full-featured, works in any browser. This is what you get at
http://yourserver:8096. - Android: The official Jellyfin app on Google Play / F-Droid. Smooth, well-maintained, supports direct play of most formats.
- Android TV / Fire TV: Official app. Works well on Shield, Fire Stick, Chromecast with Google TV. Supports external player integration (mpv, VLC) for codecs it can't handle natively.
Good
- iOS / iPadOS: The official app is functional and improving. Not as polished as Plex's iOS app but perfectly usable for daily use.
- Swiftfin: A third-party native iOS/tvOS client that's more polished than the official app. Worth trying if you're on Apple devices.
- Jellyfin Media Player: Desktop client based on mpv. Available for Windows, macOS, and Linux. Handles every codec and format without transcoding.
Other Options
- Roku: Official Jellyfin app available in the Roku Channel Store.
- Kodi: Jellyfin has an official Kodi add-on that syncs your library to Kodi. This gives you Kodi's excellent playback engine with Jellyfin's library management.
- Samsung/LG Smart TVs: No official app. Use the web client in the TV's browser, or get a Fire Stick / Chromecast.
The Client App Gap with Plex
This is where Plex genuinely wins. Plex has native apps on virtually every platform — smart TVs, game consoles (PlayStation, Xbox), and they're all polished. If your primary consumption device is a Samsung TV or a PlayStation, Plex is the path of least resistance. Jellyfin's strategy relies more on Android TV devices and streaming sticks, which is a reasonable workaround.
Remote Access
Accessing your media server outside your home requires exposing Jellyfin to the internet. There are several approaches, ordered from most to least secure:
Tailscale / WireGuard VPN (Recommended)
The safest option. Set up a VPN and your remote devices access Jellyfin as if they were on your home network. No ports exposed to the internet.
# Install Tailscale on your Jellyfin server
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# Your Jellyfin server gets a Tailscale IP (e.g., 100.x.y.z)
# Access Jellyfin from anywhere: http://100.x.y.z:8096
The downside: every client device needs Tailscale or WireGuard installed. This works great for your own phone and laptop, but it's a pain for family members' devices.
Reverse Proxy with SSL
Expose Jellyfin through a reverse proxy (Traefik, Nginx, Caddy) with HTTPS. This requires a domain name and port forwarding on your router.
# Nginx reverse proxy config
server {
listen 443 ssl;
server_name jellyfin.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/jellyfin.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jellyfin.yourdomain.com/privkey.pem;
location / {
proxy_pass http://192.168.1.50:8096;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (required for Jellyfin)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Cloudflare Tunnel
Cloudflare Tunnels expose your Jellyfin instance without opening any ports on your router. Traffic routes through Cloudflare's network.
# Install cloudflared and authenticate
cloudflared tunnel create jellyfin
cloudflared tunnel route dns jellyfin jellyfin.yourdomain.com
# Configure the tunnel
# ~/.cloudflared/config.yml
tunnel: <tunnel-id>
credentials-file: /path/to/credentials.json
ingress:
- hostname: jellyfin.yourdomain.com
service: http://localhost:8096
- service: http_status:404
Note: Cloudflare's terms of service technically prohibit using their free tier for streaming large video files. Many people do it anyway for personal use, but be aware of the limitation.
Useful Plugins
Jellyfin's plugin system extends its capabilities. Install them from Dashboard > Plugins > Catalog.
- Open Subtitles: Automatically download subtitles from OpenSubtitles.org. You'll need a free API key.
- Fanart: Additional artwork and backgrounds from fanart.tv.
- Playback Reporting: Statistics on what's being watched — useful if you're curious about viewing habits.
- Trakt: Sync watch history with Trakt.tv for tracking across platforms.
- LDAP Authentication: If you're running an LDAP/Active Directory server (yes, some home labs go that deep), you can use it for Jellyfin auth.
- Intro Skipper: Automatically detects and marks TV show intros so you can skip them with one click. Crowd favorite.
Maintenance and Performance
Regular Tasks
# Update Jellyfin (Docker)
docker compose pull && docker compose up -d
# Check Jellyfin logs
docker logs jellyfin --tail 100 -f
# Clean up transcoding cache (can grow large)
# Jellyfin usually handles this, but if your cache drive fills up:
rm -rf ./cache/transcodes/*
Performance Tuning
- Separate cache and config drives: If possible, put Jellyfin's cache directory on an SSD. Transcoding writes temporary files to cache, and an HDD will bottleneck transcoding speed.
- RAM: Jellyfin itself uses modest RAM (500MB-1GB), but hardware transcoding benefits from having spare system memory. 4 GB minimum, 8 GB comfortable.
- Network: Streaming 4K content uses 50-80 Mbps. Make sure your server has a wired gigabit connection — don't rely on WiFi for the server side.
- Storage throughput: Multiple simultaneous streams from an HDD can cause buffering. An SSD cache or direct SSD storage for your media avoids this.
User Management
Jellyfin supports multiple user accounts with per-user library access and parental controls:
- Go to Dashboard > Users
- Create accounts for family members
- Set per-user permissions: which libraries they can see, whether they can download, maximum streaming bitrate, parental rating limits
- Each user gets their own watch history and "Continue Watching" queue
Migration from Plex
If you're coming from Plex, here's what to expect:
- Library structure: If your media is already organized with proper naming (Plex and Jellyfin use the same conventions), just point Jellyfin at the same directories.
- Watch history: There's no built-in migration tool, but you can use Trakt as a bridge — sync your Plex history to Trakt, then sync Trakt to Jellyfin.
- Client apps: This is the biggest adjustment. Plex's smart TV apps don't have a Jellyfin equivalent. You may need a streaming device (Fire Stick, Chromecast with Google TV) for TVs without good Jellyfin support.
Jellyfin is the media server that respects your ownership. No accounts to create, no subscriptions to manage, no company deciding which features you get. Your media, your server, your rules. For a home lab, that philosophy fits perfectly.