← All Guides
beginner

Self-Hosting Jellyfin: Complete Media Server Setup Guide

Set up Jellyfin on Docker with hardware transcoding, organize your library, and access it from any device. Full walkthrough with working Docker Compose config.

Budget Homelab ·
dockerhow-tobeginnerself-hosting

This post contains affiliate links. If you buy through them, I earn a small commission at no extra cost to you.

Jellyfin is a completely free, open-source media server. No Plex Pass. No subscriptions. No telemetry. You point it at your media files and it serves them to any device (phone, TV, browser, desktop app) with a polished interface, automatic metadata, and hardware-accelerated transcoding.

If you have movie or TV files sitting on a hard drive, Jellyfin is the fastest path from “files on disk” to “streaming service on your couch.”

How Jellyfin Works

Jellyfin has two halves: a server and clients. The server is one program on a machine that stays on. You point it at your media folders, and it scans them, matches each file against metadata providers such as The Movie Database and TheTVDB, and builds a library with posters, descriptions, and watch history. Clients are the apps on your TV, phone, and browser. They only talk to the server over your network, and the server does the heavy lifting, including transcoding when a client cannot play a file as it is. Accounts are local to your server, so nothing about signing in depends on an outside service.

Quick Answer: How to Self-Host Jellyfin

Install Jellyfin on a Linux server using Docker Compose. The whole setup takes under an hour.

  1. Create directories for Jellyfin config, cache, and your media library on the server
  2. Write a docker-compose.yml using the jellyfin/jellyfin image, mapping port 8096 and mounting your media paths
  3. Run docker compose up -d and navigate to http://your-server-ip:8096 to complete the wizard
  4. Add your media libraries during setup: point Jellyfin at your movies, TV shows, and music folders
  5. Enable hardware transcoding in Dashboard > Playback > Transcoding (Intel Quick Sync or AMD depending on your CPU)
  6. Set up remote access via a reverse proxy with SSL for outside-the-home streaming

An N100 mini PC like the Beelink EQ12 Pro handles Jellyfin plus several other services simultaneously. The Best Mini PCs for a Budget Homelab in 2026 covers hardware picks if you’re still deciding what to run this on.

This guide uses Docker Compose. If you haven’t set up Docker yet, start with the Getting Started guide and Docker Compose basics. If you’re still deciding how to run Jellyfin (dedicated VM, LXC, or bare metal), the Proxmox VM vs LXC guide covers that decision. For remote access after setup, add Tailscale.

Prerequisites

Other Ways to Install Jellyfin

This guide uses Docker Compose because updates and backups are one command each. If Docker is not the right fit, Jellyfin’s installation docs cover these paths, and each one is short enough to follow from the official page:

If you are still choosing hardware, Jellyfin’s hardware selection guide is worth a read first.


Step 1: Create the directory structure

mkdir -p /opt/jellyfin/{config,cache}
mkdir -p /data/media/{movies,tvshows,music}

What these are:

Adjust the media paths to wherever your actual files live. If you’re still figuring out where to store media, decide on a location before going further: a dedicated drive or directory under /data/media is the standard pattern.


Step 2: Write the Docker Compose file

Create /opt/jellyfin/docker-compose.yml:

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    user: "1000:1000"
    network_mode: host
    volumes:
      - /opt/jellyfin/config:/config
      - /opt/jellyfin/cache:/cache
      - /data/media:/media:ro
    environment:
      - JELLYFIN_PublishedServerUrl=http://YOUR_SERVER_IP:8096
    restart: unless-stopped

A few notes on these config choices:

network_mode: host: Jellyfin’s client auto-discovery is a UDP broadcast on port 7359 that only works inside your local subnet, and host networking is the simplest way to keep it working. It also means Jellyfin answers on your server’s own IP address, so there are no ports: entries to write. If you would rather use Docker’s bridge network, remove network_mode: host and publish 8096:8096 instead, and add 8920:8920 only if you turn on HTTPS. In bridge mode, discovery may not reach your apps, so type the server address in by hand.

user: "1000:1000": Runs Jellyfin as your regular user rather than root. Replace 1000:1000 with your actual user and group IDs (run id in the terminal to check). Your media directory needs to be readable by this user.

/media:ro: The :ro flag mounts your media read-only. Jellyfin doesn’t need to write to your media directories.

JELLYFIN_PublishedServerUrl: Replace YOUR_SERVER_IP with the actual IP of your server on your local network.


Step 3: Start Jellyfin

cd /opt/jellyfin
docker compose up -d

Check the logs to confirm it started cleanly:

docker compose logs -f jellyfin

You should see lines like [INF] Startup complete within 30 seconds. Hit Ctrl+C to exit the log follow.


Step 4: Initial setup wizard

Open a browser and go to http://YOUR_SERVER_IP:8096. The Jellyfin setup wizard will walk you through:

  1. Creating an admin account (use a strong password)
  2. Adding media libraries (point each one at the relevant subdirectory under /media)
  3. Setting up metadata providers

Jellyfin will immediately start scanning and fetching metadata. For a large library, the initial scan can take 15-30 minutes. You can start watching while it runs.


Ports and Finding Your Server’s Address

Three ports matter, and they come straight from Jellyfin’s networking documentation:

PortProtocolConfigurableWhat it does
8096TCPYesDefault HTTP. The web interface and every app connect here.
8920TCPYesDefault HTTPS. Not used until you enable HTTPS and supply a certificate.
7359UDPNoClient discovery. Apps on the same local network use it to find the server.

Both TCP ports can be changed under Dashboard > Networking. Discovery only works inside your local subnet, and the docs say it should not be exposed to the internet.

Jellyfin has no default IP address. It answers on whatever address your server has, on port 8096. To find that address:

hostname -I

or, for a cleaner view of each interface:

ip -4 -br addr

You can also look the server up in your router’s list of connected devices. Reserve that address for the server in your router’s DHCP settings, or set a static one, so it does not change after a reboot. Then every app can use http://that-address:8096.

A raw IP address works, but a name is easier to live with. The local hostname section below covers that.


Step 5: Enable hardware transcoding

Hardware transcoding lets Jellyfin hand video encoding to a GPU instead of the CPU. It only matters when a stream has to be transcoded. If your apps can play your files as they are, the server never transcodes. On a low-power CPU, though, a single 4K software transcode can be a struggle, which is why this step is worth doing.

This guide covers the short version. The dedicated Jellyfin hardware transcoding with Intel Quick Sync guide owns the full walkthrough: permissions, LXC passthrough, tone-mapping, and how to confirm the GPU is doing the work.

1. Check that the device exists on the host:

ls /dev/dri/

You should see renderD128. If it is there, add this under the jellyfin service in your compose file:

    devices:
      - /dev/dri/renderD128:/dev/dri/renderD128
    group_add:
      - "render"
      - "video"

2. Pick the matching method under Dashboard > Playback > Transcoding:

HardwareSetting in JellyfinNotes
Intel integrated graphicsIntel QuickSync (QSV) or VAAPISame /dev/dri/renderD128 mapping as above
AMD integrated or discrete graphicsVideo Acceleration API (VAAPI)Same device mapping and group_add
NVIDIA graphics cardNVIDIA NVENCNeeds the NVIDIA Container Toolkit on the host, not a /dev/dri mapping

Jellyfin’s hardware acceleration documentation lists every supported method and the codecs each one handles, so check your exact chip there.

3. Verify it. Start a stream that forces a transcode, then open Dashboard > Active Sessions. A hardware transcode is labeled with (hw). If it says Transcode without it, acceleration is not engaging, and the full guide above walks through why.


Step 6: Set up remote access

Jellyfin is entirely self-hosted, so reaching it from outside your home is something you build. There is no relay service doing it for you. Jellyfin’s documentation lists the common approaches as forwarding its ports directly (which it says is not recommended), forwarding through a reverse proxy, using a VPN, or using a VPS as a reverse proxy to your home network. For a home server, three of those are worth comparing:

Tailscale (VPN)Reverse proxy with a domainDirect port forward
Exposed to the internetNothingThe proxy, on ports 80 and 443Jellyfin’s own port
What each viewing device needsThe Tailscale app, signed in to your tailnetJust the Jellyfin app and your URLJust the Jellyfin app and your address
HTTPSNot needed for Tailscale traffic, which is encrypted end to endA real certificate on the proxy, usually Let’s EncryptJellyfin’s own HTTPS needs a certificate you supply. Jellyfin’s docs recommend terminating HTTPS on a proxy instead
Setup effortLowestHighest: domain, DNS, certificate, proxy host, Known ProxiesLow, and the weakest option
Good forYou and a household whose devices you controlSharing with people who will not install a VPN appNot recommended by Jellyfin’s docs

Tailscale is the one I would start with, for the reason in the table: nothing is exposed. The tradeoff is that not every TV or streaming stick can run a Tailscale client, so check the platforms in your house first. The WireGuard vs Tailscale comparison covers the VPN choice, and Tailscale subnet routing covers reaching your whole home network through one device.

Option A: Tailscale (recommended)

Install Tailscale on your server and on each device you want to stream to. Once connected to your tailnet, access Jellyfin at http://100.x.x.x:8096 (your server’s Tailscale IP) from anywhere. No port forwarding and no certificate setup.

See the Tailscale setup guide for the setup. Update JELLYFIN_PublishedServerUrl in your compose file to use your Tailscale IP so the app always gets a reachable address regardless of which network you’re on.

Option B: Nginx Proxy Manager + domain

If you want a URL like https://jellyfin.yourdomain.com, set up NPM and add a proxy host that forwards to your Jellyfin server’s LAN IP on port 8096. Because the compose file above uses host networking, the container name jellyfin does not resolve from another stack, so use the IP address. Turn on Websockets Support in the proxy host, since Jellyfin uses websockets and its docs say your proxy must allow them. See the NPM setup guide for SSL and reverse proxy configuration.

Then tell Jellyfin about the proxy. Jellyfin ignores forwarded-for headers unless they come from a known proxy, so add the proxy’s IP address under Dashboard > Networking > Known Proxies. Jellyfin’s reverse proxy documentation explains why, and its remote access settings and per-user “allow remote connections” setting depend on it working.

Option C: Port forward. Jellyfin’s own documentation says opening a port directly to the internet is not recommended, and this guide agrees. If you are weighing it only because the other two seem like work, Tailscale is the lighter lift.


Give Jellyfin a Local Hostname

You can reach Jellyfin by name on your own network, without any internet exposure, by combining a local DNS record with the reverse proxy from Option B. Jellyfin’s networking docs show the same idea with http://jellyfin.internal:8096. The steps:

  1. Fix the server’s address. Reserve the IP in your router or set a static one, so the DNS record never points at the wrong place.
  2. Add a DNS record. In your local DNS server, create an A record such as jellyfin.homelab.lan. Point it at the reverse proxy if you have one, or straight at the Jellyfin server if you do not. The Technitium DNS guide walks through creating an internal zone and records, including a wildcard record that sends every subdomain to your proxy.
  3. Add the proxy host. In Nginx Proxy Manager, create a proxy host for jellyfin.homelab.lan that forwards to the Jellyfin server on port 8096, with Websockets Support on. Add the proxy’s IP under Known Proxies in Jellyfin, as above.
  4. Use the name in your apps. Without a proxy, the address is http://jellyfin.homelab.lan:8096. With one, it is https://jellyfin.homelab.lan.

Two things to decide on purpose:


Step 7: Install Jellyfin apps

Jellyfin’s clients page lists the official apps:

Apple TV is covered by Swiftfin, the Jellyfin project’s iOS and tvOS client. Any device with a browser can use the web interface instead. The official clients page does not list a PlayStation app, so check your console before you plan around it.

For the best experience on a TV, use the native app for your platform rather than the browser. Native apps handle playback formats better and usually buffer better. For a side-by-side look at Jellyfin’s app coverage against Plex’s, see Plex vs Jellyfin.


Organizing your media

Jellyfin uses filenames to identify media and match against metadata providers (The Movie Database, TheTVDB). The more closely your filenames match the expected format, the better the matching works.

Movies:

/media/movies/The Matrix (1999)/The Matrix (1999).mkv
/media/movies/Inception (2010)/Inception (2010).mkv

TV Shows:

/media/tvshows/Breaking Bad/Season 01/Breaking Bad S01E01.mkv
/media/tvshows/Breaking Bad/Season 01/Breaking Bad S01E02.mkv

The year in parentheses helps Jellyfin distinguish between films with the same name. The S01E01 format is the most reliably detected episode naming scheme. If you have a large existing library in a different format, add a small test folder first, verify Jellyfin identifies it correctly, then move files in batches. FileBot is the standard tool for bulk renaming.


Backups

Back up /opt/jellyfin/config. That directory contains your user accounts, library configuration, watched status, and metadata cache. If you lose it, you’re rebuilding from scratch.

A simple approach: add a cron job that tarballs the config directory daily and copies it off the server:

0 3 * * * tar -czf /backup/jellyfin-config-$(date +\%Y\%m\%d).tar.gz /opt/jellyfin/config

Keep a week of daily backups. Jellyfin configs are small; a week of backups is typically a few hundred megabytes at most. The homelab backup guide covers the broader backup strategy.


Updating Jellyfin

cd /opt/jellyfin
docker compose pull
docker compose up -d

Jellyfin releases frequently. The latest tag makes updates trivial. For automated container updates, Watchtower can handle this for you.


Useful settings to configure after setup

Networking > Enable automatic port mapping: Turn this off. You don’t need UPnP opening ports on your router.

Libraries > Refresh metadata: If Jellyfin pulled wrong metadata for a movie (common with obscure titles), right-click the item > “Identify” and search manually.

Users > Access schedules: Useful if you want to restrict certain users (kids) to specific hours or libraries.

Dashboard > Scheduled Tasks: Jellyfin runs metadata refresh, library scan, and cleanup tasks on a schedule. Check these and adjust if your server is doing too much at inconvenient times.


Troubleshooting common issues

“Playback error” or constant buffering

Usually transcoding. Check Dashboard > Active Sessions. If it says “Transcode” without “(hw)”, your hardware transcoding isn’t enabled or isn’t working. Fall back to software transcoding temporarily by setting the transcoding setting to “None (Software).” If playback works, the issue is hardware transcoding config.

Metadata is wrong

Right-click the item > “Identify”. Search by IMDB ID if the title is ambiguous. For TV shows, make sure your folder structure matches the expected format (Season XX).

Container won’t start

Check docker compose logs jellyfin. The most common cause is a permissions problem: the Jellyfin container runs as a specific UID and needs read access to your media directories. If your media is owned by root, either chmod o+r the directory or run Jellyfin with user: "root" in the Compose file (less ideal but functional).


Once Jellyfin is running, you have a media server with no subscription, no account, and full control over your data. If you’re curious how it stacks up, Plex vs Jellyfin covers the differences in detail. If that breakdown lands you on the other side of it, Plex Media Server on a mini PC is the equivalent walkthrough.

For monitoring server health during transcoding-heavy usage, Grafana + Prometheus gives you the CPU/GPU dashboards worth having. Immich, Vaultwarden, Paperless-ngx, and Uptime Kuma are the other high-value services worth adding next. If you built the entire homelab stack for under $300 and want to see what else fits on the same hardware, the budget homelab build guide lists 12 services that run simultaneously on an N100 box.