Jellyfin turns a folder of movies, shows and music into a polished, Netflix-style library, with posters, descriptions, "continue watching", and apps for phones, TVs and browsers. It's fully free and open source, with no account at a company in between.
VPS or home server? Decide first
Jellyfin is one of the few apps where I'd tell most people not to use a cheap VPS:
- Storage: media libraries are big, and VPS disk space is expensive per gigabyte. A 4 TB drive at home costs about what a few months of extra VPS storage would.
- Transcoding: when a device can't play a file directly, Jellyfin converts it live. That's heavy CPU work, and most VPS plans have no GPU to help.
- Bandwidth: streaming video uses a lot of it, and some providers cap or throttle it.
A small mini PC with an Intel CPU (Quick Sync handles transcoding beautifully) is the sweet spot. That said, the steps below are identical on a VPS, and a VPS works fine for a modest library that your devices can direct-play.
1. Lay out your media folders
Jellyfin identifies films and episodes from folder and file names, so a tidy layout saves hours of fixing matches later:
/srv/media/
├── movies/
│ └── The Matrix (1999)/
│ └── The Matrix (1999).mkv
├── shows/
│ └── Breaking Bad/
│ └── Season 01/
│ └── Breaking Bad S01E01.mkv
└── music/
└── Artist/Album/01 - Track.flac
sudo mkdir -p /srv/media/{movies,shows,music}
2. Write the Compose file
sudo mkdir -p /opt/jellyfin
cd /opt/jellyfin
sudo nano compose.yml
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
user: "1000:1000" # run as your user, not root (check with: id)
volumes:
- ./config:/config
- ./cache:/cache
- /srv/media:/media:ro # read-only: Jellyfin never needs to change your files
ports:
- "127.0.0.1:8096:8096" # localhost only, for the tunnel
networks:
- default
- npm
networks:
npm:
external: true
name: npm_default
sudo mkdir -p config cache
sudo chown -R 1000:1000 config cache # match the user: line above
sudo docker compose up -d
Mounting the media read-only is a small habit with a big payoff: a bug, a bad plugin or a mis-click in the web UI can never delete your library.
3. Run the setup wizard
On your own computer:
ssh -L 8096:localhost:8096 your-user@your-server-ip
# then open http://localhost:8096
The wizard asks for an admin user, then your libraries. Add one per folder: content type Movies → /media/movies, Shows → /media/shows, Music → /media/music. Jellyfin scans them and fetches artwork.
4. Watch from anywhere
In Nginx Proxy Manager, add a proxy host for media.example.com → http · jellyfin · 8096, with Websockets Support on and an SSL certificate with Force SSL. Then install the Jellyfin app on your phone or TV and enter that URL.
In Jellyfin's Dashboard → Networking, it's worth setting the Known proxies field to NPM so Jellyfin sees viewers' real IP addresses rather than the proxy's.
5. Updates and backups
cd /opt/jellyfin
sudo docker compose pull && sudo docker compose up -d
Back up /opt/jellyfin/config: it holds your users, watch history, library settings and metadata. The cache folder can be rebuilt and doesn't need backing up. Whether to back up the media itself depends on whether you could get it again. Home videos, yes. A ripped DVD on your shelf, maybe not.
Troubleshooting
- Libraries show up empty: a permissions problem. The
user:in compose must be able to read/srv/media(ls -l /srv/media). - Wrong posters or mismatched titles: rename the folder to
Title (Year)and use Identify on the item. - Playback stutters on one device only: it's transcoding. In playback info, check whether it's "Direct playing". Lowering the quality in the client, or converting that file once, helps more than anything server-side.
Official documentation: Jellyfin docs · Repository: jellyfin/jellyfin