Skip to content

TutorialsSelf-hosted apps

How to install Jellyfin with Docker and HTTPS

Run the Jellyfin media server with the official Docker image, publish it over HTTPS with Caddy, upload media with rsync, back up its config and update it.

  • Intermediate
  • 30 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

This guide is not available in your language yet, so it is shown in English.

On this page
  1. Prerequisites
  2. Step 1 — Create the folders
  3. Step 2 — Write the Compose file
  4. Step 3 — Start Jellyfin
  5. Step 4 — Finish the setup wizard through an SSH tunnel
  6. Step 5 — Publish Jellyfin over HTTPS with Caddy
  7. Step 6 — Upload your media
  8. Transcoding and hardware acceleration
  9. Back up and restore
  10. Update Jellyfin
  11. Alternative — install from the official repository
  12. Troubleshooting
  13. Libraries stay empty after a scan
  14. Playback stutters and the CPU is at 100 percent
  15. Every client shows the same IP address
  16. Permission denied errors on /config at startup
  17. The server refuses to start after moving or restoring the config
  18. Next steps

Jellyfin is a free, open-source media server. It organises your movies, shows and music, fetches artwork and metadata, and streams everything to its web interface and to apps for phones, TVs and set-top boxes. There are no accounts with a vendor and no paid tiers.

This guide runs Jellyfin from the official Docker image jellyfin/jellyfin with Docker Compose. You keep the web port 8096 on localhost, finish the setup wizard through an SSH tunnel, publish Jellyfin over HTTPS with Caddy, upload media with rsync, and learn why transcoding matters on a server without a GPU. The guide also covers backups, updates and the official Debian and Ubuntu repository as an alternative.

Prerequisites

Jellyfin's hardware guide gives these recommendations for a typical deployment:

ResourceMinimum (official)Suggested starting point
RAM8 GB recommended for an average deployment; 4 GB may be enough on a headless Linux server4 GB for direct play to a few users
CPUAny modern 4-thread CPU when hardware acceleration handles video; software transcoding of HEVC, VP9 or AV1 is very demanding4 vCPUs, more if you must transcode
Disk100 GB SSD for the OS, Jellyfin's files and the transcoding cache100 GB SSD plus storage for your media

Step 1 — Create the folders

Jellyfin needs a config folder, a cache folder and your media. Keep the app in /opt/jellyfin and the media in /srv/media, or on a separate data disk mounted there:

Bash
sudo mkdir -p /opt/jellyfin/config /opt/jellyfin/cache
sudo mkdir -p /srv/media/movies /srv/media/shows /srv/media/music
sudo chown -R $USER:$USER /opt/jellyfin /srv/media
id -u
id -g

The last two commands print your user and group IDs, usually 1000. The container runs with these IDs instead of root, as the Jellyfin documentation recommends, so it can read and write these folders without extra permissions.

Step 2 — Write the Compose file

Create /opt/jellyfin/compose.yaml. It follows the official example with three changes for an internet server: the container runs as your user, port 8096 is published on localhost only, and the discovery port 7359/udp is left out because it is meant for local networks only:

YAML
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    user: "1000:1000"
    ports:
      - "127.0.0.1:8096:8096/tcp"
    volumes:
      - /opt/jellyfin/config:/config
      - /opt/jellyfin/cache:/cache
      - type: bind
        source: /srv/media
        target: /media
        read_only: true
    restart: unless-stopped

Replace 1000:1000 if id printed other values. The media folder is mounted read-only, so Jellyfin can never change or delete your files; remove read_only: true if you want Jellyfin to save artwork next to the media or to delete items from the interface.

The latest tag follows the newest stable release, including major version changes. For more control, pin a major or major.minor tag; the container documentation describes the tag scheme, and Docker Hub lists the current tags.

Step 3 — Start Jellyfin

Bash
cd /opt/jellyfin
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8096

docker compose ps should show the container as running and, after a minute, healthy. The curl command should print HTTP response headers rather than a connection error. If not, read the log with docker compose logs -f jellyfin.

Step 4 — Finish the setup wizard through an SSH tunnel

Until the wizard is finished, anyone who reaches Jellyfin could create the administrator account. Do it through a tunnel before Caddy publishes the server. On your own computer, run:

Bash
ssh -L 8096:127.0.0.1:8096 admin@203.0.113.10

Open http://localhost:8096 and go through the wizard:

  1. Select language for this client.
  2. Set up the administrator account with a strong password.
  3. Add media libraries: choose a content type, a display name and the folder, for example /media/movies for movies and /media/shows for shows. You can also skip this and add libraries later.
  4. Set a preferred metadata language and region.
  5. Networking: keep Allow remote access to this server on, and leave automatic port mapping off. It relies on UPnP, which a data-centre server neither has nor needs.

Step 5 — Publish Jellyfin over HTTPS with Caddy

Open the web ports and add a site block to /etc/caddy/Caddyfile. This is the basic example from Jellyfin's Caddy documentation:

Caddyfile
media.example.com {
    reverse_proxy 127.0.0.1:8096
}
Bash
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo systemctl reload caddy
curl -I https://media.example.com

Allowing 443 for both TCP and UDP lets clients use HTTP/3. You should get an HTTP response with a valid certificate.

Jellyfin only trusts the X-Forwarded-For header from proxies it knows; otherwise every client appears with the proxy's address. Because the port is published by Docker, requests from Caddy reach the container from the gateway address of the Compose network. Find it:

Bash
docker network inspect jellyfin_default | grep Gateway

Add that address, for example 172.18.0.1, under Dashboard → Networking → Known proxies, save, and restart the container with docker compose restart. Clients now connect with https://media.example.com in the Jellyfin apps.

Step 6 — Upload your media

Copy media from your computer over SSH with rsync, which resumes interrupted transfers and only sends changed files:

Bash
rsync -avP ~/Videos/Movies/ admin@203.0.113.10:/srv/media/movies/

Graphical SFTP clients that support SSH keys work as well. Jellyfin identifies titles most reliably when folders and files follow the naming scheme in its documentation, for example one folder per movie with the title and year. After uploading, start a scan under Dashboard → Libraries, or wait for the scheduled scan.

Transcoding and hardware acceleration

Jellyfin can deliver a file in four ways. Direct Play sends the file unchanged and adds almost no server load. Remux changes only the container, Direct Stream converts only the audio, and Transcode re-encodes the video, which is by far the heaviest. The dashboard shows which method each active stream uses.

Most virtual servers have no GPU, so any video transcoding runs on the CPU. Jellyfin's hardware guide warns that HEVC, VP9 and AV1 transcoding is very demanding even on modern CPUs, and that HDR tone mapping always calls for a GPU. On such a server:

  • store media in formats your players support, so most sessions use direct play;
  • limit expensive sessions per user: under Dashboard → Users, open a user and clear Allow audio/video playback that requires transcoding in the Media Playback section of the profile;
  • watch the CPU with docker stats jellyfin while a stream runs.

Hardware acceleration moves transcoding to a supported GPU. Jellyfin supports NVIDIA NVENC, Intel Quick Sync, VAAPI, AMD AMF and Rockchip RKMPP, and the official image already ships Jellyfin's FFmpeg build. You need a server with a supported GPU and must pass the device into the container as described in Jellyfin's hardware acceleration documentation. See GPU servers for hardware with a supported NVIDIA GPU.

Back up and restore

In the official image, the config folder holds both Jellyfin's data (database, users, watch history, metadata) and its configuration. The cache folder can be rebuilt and does not need a backup. Back up your media separately; it is usually far larger and changes rarely.

Built-in backups. Since 10.11, Dashboard → Backups → Create Backup writes a zip archive with the database and, optionally, metadata, subtitles and trickplay images. Keep at least 5 GB free for it. The archives land in the backups folder of Jellyfin's data directory, inside /opt/jellyfin/config in this setup, and the same tab offers a restore button.

Full copy. Stop the container so the database is not in use, archive the config folder and start it again:

Bash
sudo mkdir -p /opt/backups
cd /opt/jellyfin
docker compose stop
sudo tar czf /opt/backups/jellyfin-config-$(date +%F).tar.gz -C /opt/jellyfin config
docker compose start

Copy the archive and, if needed, your media to another machine with rsync. To restore, stop Jellyfin, move the current folder aside and unpack the backup. Use the same Jellyfin version that created the backup:

Bash
cd /opt/jellyfin
docker compose down
sudo mv config config.old
sudo tar xzf /opt/backups/jellyfin-config-2026-10-09.tar.gz -C /opt/jellyfin
docker compose up -d

Update Jellyfin

Read the release notes on GitHub before updating. Major releases can include database migrations that cannot be undone without a restore, and the notes list the supported upgrade paths and any plugins to remove first. Make a full backup, then pull the new image:

Bash
cd /opt/jellyfin
docker compose pull
docker compose up -d
docker image prune

If you pinned a tag, change it in compose.yaml first. Keep Docker itself updated with apt.

Alternative — install from the official repository

Jellyfin also publishes packages for Debian and Ubuntu (including Debian 12 and 13 and Ubuntu 24.04 and 26.04) on amd64, arm64 and armhf. The official install script checks your system, needs at least 2 GiB free in /var/lib and /tmp, adds Jellyfin's signing key to /etc/apt/keyrings/jellyfin.gpg, writes the repository to /etc/apt/sources.list.d/jellyfin.sources and installs the jellyfin package, which pulls in the server, the web client and Jellyfin's FFmpeg. Verify and read it before running it, as the documentation shows:

Bash
curl -s https://repo.jellyfin.org/install-debuntu.sh -O
curl -s https://repo.jellyfin.org/install-debuntu.sh.sha256sum -O
sha256sum -c install-debuntu.sh.sha256sum
less install-debuntu.sh
sudo bash install-debuntu.sh

sha256sum must print install-debuntu.sh: OK. The native service listens on port 8096 on all addresses; ufw blocks it as long as you do not allow the port, and the Caddy block above works unchanged. Data lives in /var/lib/jellyfin and configuration in /etc/jellyfin, so back up both with the service stopped (sudo systemctl stop jellyfin). Updates arrive with sudo apt update && sudo apt upgrade.

Troubleshooting

Libraries stay empty after a scan

Jellyfin sees the media under /media inside the container, not under /srv/media. Check with docker exec jellyfin ls /media/movies. If the folder is empty or access is denied, fix the bind mount path in compose.yaml or the file permissions with sudo chown -R 1000:1000 /srv/media.

Playback stutters and the CPU is at 100 percent

The stream is being transcoded. Check the playback method in the dashboard. Use a client that supports the file's codecs, lower the streaming quality in the client, or disable transcoding for that user as described above.

Every client shows the same IP address

Caddy's address is not in Known proxies, so Jellyfin ignores the forwarded headers. Add the gateway address from Step 5 and restart the container.

Permission denied errors on /config at startup

The folders belong to a different user than the one in user:. Run sudo chown -R 1000:1000 /opt/jellyfin/config /opt/jellyfin/cache with the IDs from your Compose file, then docker compose up -d.

The server refuses to start after moving or restoring the config

Recent releases refuse to start a server that was already set up when its database is missing or empty. Make sure the /config mount points to the folder that contains your restored data, not to a new empty folder.

Next steps

Frequently asked questions

Can Jellyfin transcode video on a VPS without a GPU?

Yes, but software transcoding uses the CPU heavily, and Jellyfin's hardware guide calls HEVC, VP9 and AV1 transcoding very demanding even on modern CPUs. On a server without a GPU, prefer direct play by storing media in formats your devices support and limit transcoding per user.

Which ports does Jellyfin use?

Port 8096 TCP serves the web interface and API over HTTP. Port 8920 is used only if you enable Jellyfin's own HTTPS, and UDP 7359 is for client discovery on a local network, which should not be exposed to the internet. Behind Caddy you only open 80 and 443.

Should I use Docker or the Debian and Ubuntu repository?

Both are official. Docker keeps Jellyfin isolated and makes updates and rollbacks a matter of changing the image. The repository method installs a native service that apt updates. This guide uses Docker and shows the repository method as an alternative.

How do I back up Jellyfin?

Since version 10.11 Jellyfin has a built-in backup in the dashboard that writes zip archives. For a full copy, stop the container and archive the config folder, which holds both data and configuration in the official image. Back up your media separately.

Which HyperDC servers can run Jellyfin?

Jellyfin runs on a HyperDC Linux VPS, VDS or dedicated server with root access. For transcoding many streams, a server with a supported GPU helps; for direct play, CPU, RAM and enough storage for your media are what matter.

Jelszó létrehozása

Please confirm