Guides

Jellyfin server: install and run your own media server on Linux

Set up a Jellyfin server on Ubuntu, Debian or Docker: official repository, port 8096, library permissions, HTTPS with nginx, CPU transcoding and backups.

Published ~10 min read
On this page

Short answer: a Jellyfin server is the free, open-source media server you run yourself. On Ubuntu or Debian, add Jellyfin’s official APT repository and run sudo apt install jellyfin; with Docker, run the jellyfin/jellyfin image with volumes for /config, /cache and your media. Then open port 8096 in a browser and finish the setup wizard.

This guide covers both install routes, the first-run wizard, library permissions, HTTPS behind nginx, and the part most guides skip: what to do about transcoding on a server without a graphics card. Commands and paths come from Jellyfin’s own documentation, read on 21 October 2026.

ItemValue
Web interfaceTCP 8096 (HTTP)
Client discoveryUDP 7359, only useful on a local network
Service and userjellyfin (systemd unit and system user)
Config (APT package)/etc/jellyfin
Data and database (APT package)/var/lib/jellyfin
Cache and transcodes (APT package)/var/cache/jellyfin
Logs (APT package)/var/log/jellyfin
Current release12.2, published 5 October 2026 (GitHub releases); check yours with apt policy jellyfin

What is a Jellyfin server?

Jellyfin is a media server: it scans folders of films, series, music and photos, fetches artwork and descriptions, and streams the files to a web browser, a phone, a TV app or Kodi. It is GPL-licensed, has no paid tier and needs no account with anyone but your own server. If you are still choosing between it and the best-known alternative, Jellyfin vs Plex compares the two; this page is about running Jellyfin once you have picked it.

People also search for “free Jellyfin servers”: lists of other people’s instances with shared logins. The software is what is free. Someone else’s server sees your IP address and everything you watch, the libraries on those lists are rarely licensed, and they disappear without notice. Your own server is the free option that lasts.

What does a Jellyfin server need?

  • Memory: Jellyfin’s hardware page recommends at least 8 GB for an average deployment and says 4 GB may be enough on a Linux server without a desktop.
  • Fast storage for Jellyfin itself: the database, metadata and image cache see a lot of random access, so the docs ask for an SSD there. The media files only need to read faster than their own bitrate, which almost any disk does.
  • CPU: light for direct play, heavy for transcoding. The transcoding section below explains why that decides how you prepare your files.
  • Disk space: the library itself, usually the largest part of the bill. The storage section has the arithmetic.

Install Jellyfin on Ubuntu or Debian

Jellyfin publishes its own APT repository. Its install script checks the release against a list (read 21 October 2026: Debian 12 and 13, Ubuntu 22.04, 24.04 and 26.04, plus two older releases for which Jellyfin 12.0 stopped building packages), adds the repository and installs the jellyfin metapackage.

  1. 1Download the script and check its checksum
    Debian · Ubuntu
    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

    The last command must print install-debuntu.sh: OK. Read the script before you run it; it is short.

  2. 2Run it
    Debian · Ubuntu
    sudo bash install-debuntu.sh

    It ends by showing the service status and the address to open: http://YOUR_IP:8096.

If you would rather not run a script as root, the manual method from the same docs does the same steps by hand. On Debian, extrepo enable jellyfin is a third way that writes the repository entry for you.

Debian · Ubuntu · manual repository
sudo apt install -y curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg

. /etc/os-release
sudo tee /etc/apt/sources.list.d/jellyfin.sources > /dev/null <<EOF
Types: deb
URIs: https://repo.jellyfin.org/${ID}
Suites: ${VERSION_CODENAME}
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/jellyfin.gpg
EOF

sudo apt update
sudo apt install -y jellyfin

On Debian 12 the heredoc renders as URIs: https://repo.jellyfin.org/debian, Suites: bookworm and Architectures: amd64. It works on Debian and Ubuntu themselves; a derivative such as Linux Mint reports its own ID, so use the script there. Then check that the service is up and listening:

Debian · Ubuntu
systemctl status jellyfin --no-pager
sudo ss -tlnp | grep 8096

The jellyfin metapackage pulls in Jellyfin’s own FFmpeg build, for the 12.x packages jellyfin-ffmpeg8 (read 21 October 2026). If you install jellyfin-server on its own, that build is only a recommended package, so check with dpkg -l | grep jellyfin-ffmpeg that it arrived; without an FFmpeg, every playback that needs conversion fails.

Install Jellyfin with Docker

The official image is jellyfin/jellyfin. The compose file below follows the one in Jellyfin’s container docs, with two changes for a public server: the port is bound to 127.0.0.1 because nginx will sit in front, and the UDP discovery port is left out because it only works on a LAN. Docker itself is covered in installing Docker on Ubuntu and Debian.

/opt/jellyfin/docker-compose.yml
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    user: "1000:1000"            # a uid:gid that can read the media
    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
    environment:
      - JELLYFIN_PublishedServerUrl=https://media.example.com
Debian · Ubuntu
sudo mkdir -p /opt/jellyfin/config /opt/jellyfin/cache
sudo chown -R 1000:1000 /opt/jellyfin/config /opt/jellyfin/cache
cd /opt/jellyfin && sudo docker compose up -d
sudo docker logs -f jellyfin

Mount the media read-only, as here, and Jellyfin cannot rename or delete your files. Its metadata goes into /config instead; if you want artwork saved next to the files, the mount has to be writable.

First run: the setup wizard

Until the wizard is finished, the first person to open port 8096 creates the administrator account. On a server with a public IP, do not leave it open, not even for a few minutes. Reach it through an SSH tunnel instead, which needs no open port at all:

your computer
ssh -L 8096:localhost:8096 user@203.0.113.10
# then open http://localhost:8096 in your local browser

The wizard walks you through these steps:

  • Display language for the rest of the setup.
  • The administrator account. Use a long, unique password: this account can read every library and change every setting.
  • Media libraries: pick a content type (Movies, Shows, Music) and add the folders. You can skip this and add them later under Dashboard, Libraries.
  • Metadata language and country for titles and descriptions.
  • Remote access: leave “Allow remote connections” on, or clients outside your own network cannot connect. A server in a data centre has no home router to map ports on; nginx below does that job.

Library folders and permissions

Jellyfin matches files to metadata by their names and folders, so a tidy layout saves hours of fixing wrong matches later. The pattern from Jellyfin’s naming docs:

/srv/media
/srv/media/movies/Night of the Living Dead (1968)/Night of the Living Dead (1968).mkv
/srv/media/shows/Example Series (2018)/Season 01/Example Series S01E01.mkv
/srv/media/music/Artist/Album/01 - Track.flac

With the APT package, Jellyfin runs as the system user jellyfin. It needs read permission on every file and read plus execute permission on every directory above them. The cleanest setup is a shared group that owns the media:

Debian · Ubuntu
sudo groupadd media
sudo usermod -aG media jellyfin
sudo usermod -aG media "$USER"          # so you can upload into it too
sudo chgrp -R media /srv/media
sudo chmod -R g+rX /srv/media
sudo find /srv/media -type d -exec chmod g+s {} +   # new files inherit the group
sudo systemctl restart jellyfin          # group changes apply to new processes only

sudo -u jellyfin ls "/srv/media/movies"  # the real test

If the last command answers Permission denied, a library scan finds nothing, with no error in the web interface. Look one directory higher: a home directory with mode 700 blocks everything below it, which is why media under /home/you so often “scans empty”. Adding a user to a group and chmod explain the pieces. With Docker the same rule applies to the user: uid and gid in the compose file.

HTTPS with nginx, including WebSockets

Put nginx in front for a real certificate on your own domain. Jellyfin’s nginx example proxies / normally and /socket with the WebSocket upgrade headers; without them, playback works but remote control, SyncPlay and live dashboard updates do not.

/etc/nginx/sites-available/jellyfin
server {
    listen 80;
    server_name media.example.com;
    client_max_body_size 20M;          # nginx default 1M can be too small for posters

    location / {
        proxy_pass http://127.0.0.1:8096;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_buffering off;
    }

    location /socket {
        proxy_pass http://127.0.0.1:8096;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
    }
}
Debian · Ubuntu
sudo ln -s /etc/nginx/sites-available/jellyfin /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d media.example.com
sudo ufw allow 'Nginx Full'              # 80 and 443; 8096 stays closed

Nginx as a reverse proxy with SSL covers certbot, renewal and the usual 502 errors in detail. Two more steps finish the job: in Jellyfin, add the proxy’s address (127.0.0.1 here) under Dashboard, Networking, Known proxies, so it logs real client IPs and can tell local from remote clients; and with the APT package, which listens on all interfaces, make sure the firewall does not allow 8096 from outside.

Remote access and clients

Every client logs in with the server address, which is now https://media.example.com. There are official or community apps for the web, Android, iOS, Android TV and Fire TV, Roku, desktop players and Kodi. Quick Connect lets a TV app log in with a short code that you enter on a device where you are already signed in, instead of typing a password with a remote.

  • One account per person, created under Dashboard, Users, with access only to the libraries they need.
  • Per-user playback rules: each user has switches for playback that requires transcoding and a remote bitrate limit. On a CPU-only server those two settings protect everyone else’s stream.
  • Brute force: Jellyfin locks an account after repeated failed logins; put fail2ban in front if you want IP bans as well.

Transcoding without a GPU: plan for direct play

Transcoding converts a file on the fly into a codec, resolution or bitrate the client can play. Jellyfin’s hardware guide is blunt about doing it without a graphics card: it is not recommended, because video transcoding on the CPU is very demanding, and HDR to SDR tone mapping in software takes a high-end processor to keep up in real time with 4K material.

Our servers have no GPU, so on a VMHeaven VPS or dedicated server every transcode runs on the CPU. That is workable if transcoding is the exception rather than the rule:

  • Prefer direct play. H.264 video with AAC audio in an MP4 or MKV plays directly on nearly every client. HEVC (H.265) saves space at the same quality but plays directly on fewer devices.
  • Pre-encode once instead of transcoding every time. Converting a file ahead of time costs the CPU once, at night if you like; transcoding costs it on every play.
  • Use text subtitles. SRT and similar text subtitles are sent to the client as text. Image-based subtitles (PGS from Blu-ray, VobSub from DVD) often have to be burned into the picture, which forces a full video transcode.
  • Leave hardware acceleration at None under Dashboard, Playback, Transcoding. There is nothing to accelerate with.
  • Check what actually happens. During playback, open Playback Info in the web player: it shows Direct playing or Transcoding, and the reason.
any Linux · FFmpeg
# Re-encode to H.264 + AAC in MP4: plays directly almost everywhere
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0 \
  -c:v libx264 -preset slow -crf 20 \
  -c:a aac -b:a 192k -movflags +faststart output.mp4

# Only the container is the problem: copy the streams, costs almost no CPU
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0 -c copy output.mp4

Installing FFmpeg covers the install and what CPU encoding with libx264 costs. Audio-only conversion, say a surround track into AAC, is cheap and rarely worth avoiding; it is the video that hurts.

Storage: how much space, and where it lives

Size a library by bitrate times running time. A film encoded at 8 Mbit/s is 1 MB per second, so two hours (7,200 seconds) come to about 7.2 GB. Episodes and lower bitrates are smaller; untouched Blu-ray and 4K remuxes are many times larger. Count your largest files, multiply, and leave room to grow.

  • Keep media on its own mount, such as /srv/media, not on the system disk. A full root file system stops Jellyfin, and everything else, at once.
  • Keep the cache on fast disk. Transcodes are written under the cache directory and can grow by gigabytes during a long session.
  • Watch both: df -h /srv/media /var/cache/jellyfin and checking disk usage when something fills up.

On a VPS the library usually outgrows the disk before anything else. A storage VPS grows the server’s disk in blocks from the panel while it keeps running; put the new space in a partition of its own and mount it at /srv/media (mounting a disk covers that step). For several terabytes on one machine, a dedicated server in Amsterdam lists its disks per build. Jellyfin is built for a household or a circle of friends; a channel that hundreds of people watch at once is a bandwidth problem, which is what Streaming Dedicated with its 10 Gbit/s unmetered port is for.

Back up the Jellyfin config

Your media is the big part, but the small part is what takes days to rebuild: users, watch history, playback positions, library settings and fixed metadata. Since 10.11 Jellyfin has a built-in backup under Dashboard, Backups, which works while the server runs. For a manual copy, stop the server first; Jellyfin’s docs warn that a copy of a running database may not be recoverable.

Debian · Ubuntu · APT package
sudo systemctl stop jellyfin
sudo tar -czf /root/jellyfin-$(date +%F).tar.gz /var/lib/jellyfin /etc/jellyfin
sudo systemctl start jellyfin
Docker
cd /opt/jellyfin && sudo docker compose stop
sudo tar -czf /root/jellyfin-$(date +%F).tar.gz /opt/jellyfin/config
sudo docker compose start

Copy the archive off the server with rsync. Take one before every major upgrade: the 12.0 release notes say its database changes cannot be rolled back without a full restore, and ask for a full library scan after the upgrade.

Common problems and fixes

SymptomUsual causeFix
Browser cannot reach :8096Service down, or the firewall blocks itCheck systemctl status jellyfin; use the SSH tunnel or nginx instead of opening the port
Library scan finds nothingThe jellyfin user cannot read the folder or a parentsudo -u jellyfin ls DIR, then fix group and modes
Playback stutters, CPU at 100%Transcoding on the CPUPlayback Info shows the reason; re-encode the file or switch to text subtitles
Remote control and SyncPlay do not workWebSocket not proxiedAdd the location /socket block with the Upgrade headers
Image upload fails with 413nginx body limitRaise client_max_body_size, see 413 Request Entity Too Large
All clients show the proxy IPProxy not trustedAdd it under Networking, Known proxies

Frequently asked

What port does a Jellyfin server use?

The web interface listens on TCP port 8096 over plain HTTP. UDP 7359 is only used for client discovery on a local network. On a public server, keep 8096 closed and put nginx with a certificate in front, or reach the server through an SSH tunnel.

Can a Jellyfin server run without a GPU?

Yes. Direct play, where the client plays the file as it is, needs very little CPU. Any transcoding then runs on the CPU alone, which Jellyfin's own hardware guide calls very demanding, so prepare files the clients can play directly: H.264 video, AAC audio and text subtitles.

Are there free Jellyfin servers?

Jellyfin itself is free and open source, with no paid tier. Public lists of free Jellyfin servers are other people's instances: the operator sees your IP address and what you watch, and the content is rarely licensed. Running your own server is the free option that lasts.

How do I back up a Jellyfin server?

Use Dashboard, Backups, built in since Jellyfin 10.11, or stop the service and copy /var/lib/jellyfin and /etc/jellyfin for the APT package, or the /config volume for Docker. Keep the copy on another machine and take one before every major upgrade.

Related articles

All guides