What is Jellyfin?
Jellyfin is a free media server that organises your movies, TV shows, music and photos and streams them to browsers, phones, TVs and set-top boxes. It is open source under the GPL-2.0 license, community-run, and has no premium tier, tracking or required online account. It is a common replacement for Plex and Emby.
Requirements
- A Linux server with 2 CPU cores and 2 GB of RAM. Software transcoding of 4K content needs a much stronger CPU, or a GPU.
- Docker Engine and Docker Compose v2.
- Your media library on a local disk or a mounted NAS share.
- Optional: an Intel CPU with Quick Sync, an AMD GPU or an NVIDIA GPU for hardware transcoding.
Step 1: Prepare the server
This guide uses Ubuntu 24.04 with Docker installed. If you still need Docker, follow the official Docker install guide. Create folders for config and cache:
mkdir -p ~/jellyfin/{config,cache} && cd ~/jellyfin
id -u && id -g
Note the two numbers printed by id. Jellyfin will run as that user, so it needs read access to your media folders.
Step 2: Create the Docker Compose file
Save this as docker-compose.yml, adjusting the media paths and the user line:
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: "1000:1000"
restart: unless-stopped
ports:
- "8096:8096"
- "7359:7359/udp"
environment:
JELLYFIN_PublishedServerUrl: "https://media.example.com"
TZ: "Europe/London"
volumes:
- ./config:/config
- ./cache:/cache
- /srv/media/movies:/media/movies:ro
- /srv/media/shows:/media/shows:ro
- /srv/media/music:/media/music:ro
# Uncomment for Intel Quick Sync or AMD VAAPI hardware transcoding:
# devices:
# - /dev/dri:/dev/dri
# group_add:
# - "993"
Mounting media read-only (:ro) protects your files from accidental changes. Jellyfin does not need write access unless you want it to save artwork and NFO files next to your media. For hardware transcoding with /dev/dri, replace 993 with the group ID of the render group (getent group render | cut -d: -f3). Jellyfin does not use any secrets in the Compose file. You create the admin password in the setup wizard.
latest tracks the current stable release. To pin a version, use a tag from Docker Hub, such as the major version tag.
Step 3: Start and open the app
docker compose up -d
Open http://YOUR_SERVER_IP:8096. The setup wizard asks for a display language, then creates the administrator account. Use a strong password, because this account can also manage the server. Next, add libraries: choose a content type (Movies, Shows, Music) and point it at the matching path inside the container, for example /media/movies. Jellyfin scans the folders and fetches metadata and artwork.
Name files the way Jellyfin expects (Movie Name (2024)/Movie Name (2024).mkv and Show Name/Season 01/Show Name S01E01.mkv) for accurate matching.
Step 4: Put it behind HTTPS
For remote access, use a reverse proxy instead of exposing port 8096. With Caddy:
media.example.com {
reverse_proxy 127.0.0.1:8096
}
Then, in Dashboard, Networking, add the proxy's IP to "Known proxies" so Jellyfin logs real client addresses and applies its LAN and remote bitrate rules correctly. Nginx Proxy Manager works too. Enable "Websockets Support" on the proxy host. When remote access works, change the mapping to "127.0.0.1:8096:8096" if you do not need direct LAN access on 8096.
Backups and upgrades
Back up the config folder. It holds the database, users, watch history, plugins and settings. The cache folder can be rebuilt and does not need a backup. Stop the container before copying config so the SQLite database is consistent:
docker compose stop && tar czf jellyfin-config-$(date +%F).tgz config && docker compose start
Upgrade with:
docker compose pull && docker compose up -d
Jellyfin migrates its database on major upgrades and cannot be downgraded afterwards, so take a config backup first.
Troubleshooting
- Libraries are empty after a scan: the container user cannot read the media path, or the library points at a host path instead of the container path (
/media/...). - Playback buffers or the CPU is pegged: the client is transcoding. Enable hardware acceleration in Dashboard, Playback, or use a client that can direct-play the format.
- Hardware transcoding fails: check that
/dev/driexists on the host and that thegroup_addID matches therendergroup. - Apps cannot auto-discover the server: UDP 7359 must be published, and discovery does not work across subnets or VPNs.
Next steps
Install the Jellyfin apps for your TV and phone, set per-user parental controls, and add plugins such as OpenSubtitles from the built-in catalog. Community repositories add extras like Intro Skipper.
Spotted something out of date? Tell us and we will update the guide.