GuidesFiles, photos and homeMedia with Jellyfin

Run Jellyfin, your own media server, with rootless Podman

Jellyfin 12 on Debian 13, the open-source media server for your own films, music and home videos, with apps for phones and TVs, in rootless Podman under a user of its own behind Caddy.

Tested on Jellyfin 12.1.0 on Debian 13 (trixie) on a Melonslab server Updated October 2, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

Jellyfin is a media server: it keeps your films, music and home videos in one library, with posters, descriptions and where you stopped watching, and plays them in a browser or in its apps for phones and TVs. Here it runs as one container under a user of its own called jellyfin, behind Caddy from the Podman guide. It is for media that is yours to play: your own videos, music from CDs you own, and freely licensed films and music.

Jellyfin is developed by a volunteer community, not a company, and takes donations through Open Collective, with Open Source Collective as its fiscal host. It is open source under the GNU GPL 2.0. It needs no account with the project. It does look things up online by default, and each lookup can be switched off (step 10):

  • TheMovieDb (TMDB), run by Xperi Inc. in California, USA: Jellyfin searches it by the film's title and year, and downloads posters from it. Its privacy policy says it collects IP addresses.
  • MusicBrainz, run by the MetaBrainz Foundation, a non-profit in California: Jellyfin looks up artists and albums. It logs the IP address, the program's name and version, and what was looked up.
  • repo.jellyfin.org, the project's plugin catalogue, at every start and once a day. In the test it redirected to a mirror in Germany, on DigitalOcean servers.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • The first-visitor setup wizard was run over an SSH tunnel. From the internet afterwards, every wizard address led to the login page, and the setup API answered 401.
  • Films and music were uploaded with rsync and SFTP as the jellyfin user, read by the container without any chown, and found by the library scan. A new file showed up by itself 60 seconds after upload.
  • Big Buck Bunny, a 1080p H.264 film at 9.7 Mbit/s, played in Chromium without any conversion, and the server stayed almost idle.
  • Converting it to 720p at 4 Mbit/s ran at 2.8 times playback speed on both cores. Two conversions at once ran at 1.7 and 1.2 times.
  • After the proxy setting in step 5, Jellyfin's activity log showed visitors' real IPv4 and IPv6 addresses.
  • With the online lookups switched off, a full metadata refresh made no outside connections.
  • The settings and database were backed up and restored, and everything came back by itself after a reboot.

Jellyfin used about 130 to 200 MB of memory when idle, 435 MB while converting one film, and 630 MB while converting two. The test films were Blender Foundation open movies (Big Buck Bunny, Sintel and Tears of Steel, CC BY) and the Tears of Steel soundtrack by Joram Letwory (CC BY-ND).

Before you start

You need:

  • a server secured and set up as in the Podman guide, with Caddy running;
  • an A record and an AAAA record for jelly.example.com pointing at your server;
  • enough disk for your media. The library takes as much space as the files you upload, and the Jellyfin image another 1.7 GB, so your plan's disk size is the size limit of your library (step 11).

The examples use jelly.example.com for Jellyfin and 203.0.113.10 for your server's IPv4 address, which ip -brief address show eth0 shows. Replace them throughout.

1. Create the user

As root:

apt install -y rsync
useradd -m -s /bin/bash jellyfin
loginctl enable-linger jellyfin

rsync is for uploading media in step 6. To upload as jellyfin with the same SSH key you use for root, copy root's key file to it:

install -d -m 700 -o jellyfin -g jellyfin /home/jellyfin/.ssh
install -m 600 -o jellyfin -g jellyfin /root/.ssh/authorized_keys /home/jellyfin/.ssh/

Then switch to the user:

machinectl shell jellyfin@

Everything up to step 4 runs as jellyfin.

2. Define the container

mkdir -p ~/.config/containers/systemd ~/jellyfin/config ~/jellyfin/cache ~/media/movies ~/media/music
echo "media-$(openssl rand -hex 8)"

The second command prints a random name, such as media-3c9f0a7d1e5b2846. Create ~/.config/containers/systemd/jellyfin.container, with your name in the /media-... part of the third Volume= line:

[Unit]
Description=Jellyfin media server

[Container]
ContainerName=jellyfin
Image=docker.io/jellyfin/jellyfin:12
# Only Caddy, on this server, can reach Jellyfin: the port is not open to the internet.
PublishPort=127.0.0.1:8113:8096
Volume=%h/jellyfin/config:/config
Volume=%h/jellyfin/cache:/cache
Volume=%h/media:/media-3c9f0a7d1e5b2846:ro
Environment=TZ=Europe/Stockholm
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

What the lines do:

  • ~/jellyfin/config holds the database, settings and downloaded posters, and ~/jellyfin/cache the images and converted video Jellyfin makes while you watch.
  • ~/media is your library. It is mounted read-only, :ro, so Jellyfin can read your files but cannot change or delete them.
  • The random folder name matters for one reason. Jellyfin plays a video to anyone who has its item ID, without a login, and an item ID is made from the file's path inside the container, so it can be worked out from a well-known film title. Tested: with /media/movies/Big Buck Bunny (2008)/Big Buck Bunny (2008).mov, the ID computed from that path played without a login. With the random folder name, nobody can compute it. Pick the name now: changing it later gives every item a new ID.
  • The 12 tag keeps Jellyfin on its newest 12.x release.

Start it:

systemctl --user daemon-reload
systemctl --user start jellyfin
systemctl --user enable --now podman-auto-update.timer

The first start downloads the image, about 1.7 GB, and takes under a minute.

3. Run the setup wizard over an SSH tunnel

Until the wizard is done, Jellyfin lets whoever opens it first create the admin account. So do it before Caddy makes it reachable, over an SSH tunnel. On your own computer:

ssh -L 8096:127.0.0.1:8113 root@203.0.113.10

Keep that open, and open http://localhost:8096 in your browser. That goes straight to Jellyfin, not through the internet. The wizard asks for:

  • Server name, which the apps show, and Preferred display language. Choose Next.
  • Username, Password and Password (confirm) for the admin account. Use a long password: this account can change everything.
  • Set up your media libraries: skip it with Next, step 7 adds them.
  • Preferred Metadata Language and Country/Region, for titles and descriptions.
  • Configure Remote Access: leave Allow remote connections to this server ticked. Through Caddy, every visitor is a remote connection.

Choose Finish, and log in. Then close the tunnel.

4. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

jelly.example.com {
    reverse_proxy 127.0.0.1:8113
}

Restart Caddy with systemctl --user restart caddy, then exit. Open https://jelly.example.com: it shows Please sign in, and the wizard's addresses lead there too.

5. Let Jellyfin trust Caddy

Log in at https://jelly.example.com, open the menu, Dashboard, then Networking:

  • Known proxies: 203.0.113.10, your server's own IPv4 address. Caddy's connections reach the container from that address, and only with it listed does Jellyfin read the visitor's real address from the X-Forwarded-For header that Caddy sends.
  • Published Server URI: https://jelly.example.com. Jellyfin then gives this address to the apps instead of its address inside the container.

Choose Save. The proxy setting takes effect after a restart. As jellyfin (machinectl shell jellyfin@):

systemctl --user restart jellyfin

To check, open Dashboard, Activity. Before the change every login came from 203.0.113.10. After it, logins show the visitor's own address, IPv4 or IPv6, and so do failed logins.

6. Upload your media

Put films in ~/media/movies, one folder per film named after its title and year, and music in ~/media/music, as artist and album folders:

media/
├── movies/
│   └── Big Buck Bunny (2008)/
│       └── Big Buck Bunny (2008).mov
└── music/
    └── Joram Letwory/
        └── Tears of Steel OST/
            ├── 01 40 years later.mp3
            └── 02 The dome.mp3

Jellyfin finds a film's description and poster from the title and year in the folder name. Its documentation describes the naming for TV series and other types.

Upload as jellyfin, never as root. From your own computer, with rsync, which can resume and only sends what changed:

rsync -av --progress ~/Videos/Movies/ jellyfin@203.0.113.10:media/movies/

Or with any SFTP program, such as FileZilla or WinSCP: log in as jellyfin with your SSH key, and upload into media. In the test, rsync moved 395 MB from an office line in 38 seconds.

Why the user matters: root inside the container is jellyfin outside it, so files that jellyfin owns belong to root in the container, and Jellyfin reads them. As jellyfin, podman unshare shows the files as the container sees them:

podman unshare ls -ln ~/media

The owner shows as 0, root. If you copy files in as root on the server, give them to jellyfin afterwards with chown -R jellyfin:jellyfin /home/jellyfin/media, as root.

7. Add your libraries

In Dashboard, Libraries, choose Add Media Library:

  • Content type: Movies. Display name: Movies.
  • Under Folders, choose the + button, and type the folder as the container sees it, /media-3c9f0a7d1e5b2846/movies with your random name. Choose OK.
  • Choose OK to create the library.

Do the same with Music and /media-3c9f0a7d1e5b2846/music. Jellyfin scans the folders straight away. Enable real time monitoring is on, so new files appear about a minute after you upload them; Scan All Libraries on the same page scans at once.

8. Watch, and what 2 vCPU can do

Jellyfin plays a file in one of two ways:

  • Direct play: the file is sent as it is, and your device decodes it. This costs the server almost nothing. Big Buck Bunny, 1080p H.264 with AAC sound at 9.7 Mbit/s, played this way in Chromium.
  • Transcoding: when the device cannot play the format, or you ask for a lower quality, Jellyfin converts the video with ffmpeg while you watch. Without a graphics card, the processor does all of it.

What was measured on 2 vCPU without a GPU:

Playbackffmpeg speedServer load
1080p H.264, direct playnonealmost idle
Sintel, H.264 video with AC3 sound the browser cannot play15.8×only the sound converted
1080p H.264 converted to 720p at 4 Mbit/s2.8×both cores full
two such conversions at once1.7× and 1.2×both cores full

A speed above 1× keeps up with playback. So this server converts one 1080p film comfortably and two at the limit; a third was not tested. 4K and HEVC files were not tested.

To avoid conversion, keep the quality at its highest in each player. In the web player, choose the settings button while a video plays, then Quality: Auto plays the original when the connection allows it, and a fixed value such as 4 Mbps makes the server convert. The apps have their own quality setting, which was not tested here. Each conversion writes a log in ~/jellyfin/config/log: FFmpeg.Transcode-... when the video was converted, FFmpeg.DirectStream-... when only the sound or the container was. To see the speed the latest one reached, as jellyfin:

grep -o "speed=[ 0-9.x]*" "$(ls -t ~/jellyfin/config/log/FFmpeg.* | head -1)" | tail -1

Converted video is written to ~/jellyfin/cache/transcodes, and Jellyfin deletes it at every start and once a day.

9. Use the apps

Jellyfin has apps for Android, iPhone and iPad, Android TV and other TVs. In each, enter https://jelly.example.com as the server, and log in. Give family members their own accounts under Dashboard, Users, so each has their own progress and favourites. The apps were not tested for this guide.

10. Choose what it fetches from the internet

Each library chooses its own online sources. In Dashboard, Libraries, choose a library, and untick what you do not want:

  • under Metadata downloaders (Movies): TheMovieDb and The Open Movie Database;
  • under Image fetchers (Movies): the same two. Embedded Image Extractor and Screen Grabber work from your own files;
  • in a music library, MusicBrainz and TheAudioDB (only the film library was tested).

Choose OK. The change applies to new files; for those already in the library, refresh their metadata. In the test, after unticking both for the film library, a full refresh of every film made no outside connection. The films then show the title from the folder name, without description or poster.

The plugin catalogue is a list on the Plugins page: open Manage Repositories and delete Jellyfin Stable. In the test, Jellyfin then made no lookups at start. You can then no longer install or update plugins from the catalogue. The ones Jellyfin comes with, such as TMDb and MusicBrainz, are part of the image and are updated with it.

During the whole test, Jellyfin contacted nothing besides the three services in the opening section. The Open Movie Database and TheAudioDB were enabled but were not contacted.

11. Disk space

Your media takes the space it takes: Jellyfin keeps the files as they are. Jellyfin's own data was small: 11 MB in ~/jellyfin/config for three films and an album, plus posters as the library grows. The image takes 1.7 GB. To see what is used and what is left, as root:

df -h /
du -sh /home/jellyfin/media /home/jellyfin/jellyfin

Dashboard also shows the disk under Paths. When the disk fills up, Jellyfin cannot write its database or converted video, so keep a few gigabytes free. The library can only be as large as your plan's disk.

12. Back up

The part you cannot get back is in ~/jellyfin/config: users, passwords, watch progress, settings and the library database. As jellyfin, stop Jellyfin for a consistent copy:

mkdir -p ~/backup
systemctl --user stop jellyfin
tar -czf ~/backup/jellyfin-config.tar.gz -C ~/jellyfin config
systemctl --user start jellyfin

That took under two seconds in the test, and the archive was 9.7 MB. To restore it, stop Jellyfin, move ~/jellyfin/config aside, unpack the archive with tar -xzf ~/backup/jellyfin-config.tar.gz -C ~/jellyfin, and start it again. Tested: the admin login and both libraries were back.

Dashboard, Backups, Create Backup makes a backup of the database too, as a zip file in ~/jellyfin/config/data/backups, which the archive above includes.

Copy the archive off the server, for example with off-site backups with restic. Whether you also back up ~/media is up to you: if the originals are on your own computer, you can upload them again.

13. Keep it up to date

podman-auto-update.timer from step 2 pulls new 12.x images once a day and restarts Jellyfin. To see whether one is waiting, as jellyfin:

podman auto-update --dry-run

The 12 tag only follows 12.x releases, so a new major version never arrives by itself. To move to one, read its release notes, make a backup as in step 12, and change the tag in jellyfin.container. Then run systemctl --user daemon-reload and systemctl --user restart jellyfin.

Troubleshooting

A library is empty after you added it. In the test, the music library added straight after the film library found nothing on its first scan. Scan All Libraries under Dashboard, Libraries found the album.

A new file does not appear. Real-time monitoring waits about 60 seconds after a change. Check that the file is in the right folder and owned by jellyfin (step 6).

The activity log shows 203.0.113.10 for every login. Known proxies is missing or Jellyfin has not been restarted since (step 5).

A film plays with stutter or keeps buffering. Look in ~/jellyfin/config/log for a new FFmpeg.Transcode-... log: if there is one, the server is converting the film, and the command in step 8 shows whether its speed stayed above 1×. Set the player's quality to Auto, or use a player that can play the format.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides