<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Media-Server – Serverküche</title><link>https://serverkueche.de/en/tags/media-server/</link><description>Media-Server – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Tue, 21 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/media-server/index.xml" rel="self" type="application/rss+xml"/><item><title>Self-hosting Jellyfin: your own media server behind Traefik</title><link>https://serverkueche.de/en/tutorials/jellyfin-media-server/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/jellyfin-media-server/</guid><description>Set up Jellyfin with Docker behind Traefik: stream your movies, series and music – with HTTPS and the library on netcup's Local Block Storage.</description><content:encoded><![CDATA[<p>Netflix, Spotify and Google Photos in one – only on your own server and without a monthly
fee: <strong>Jellyfin</strong> streams your movie, series and music collection to any device. In this
recipe we hang Jellyfin behind Traefik and put the library on netcup&rsquo;s <strong>Local Block
Storage</strong> so you don&rsquo;t run out of space.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Jellyfin 10.11</strong> runs as a container on your server, reachable at
<code>https://jellyfin.YOUR_DOMAIN</code> with valid HTTPS. Jellyfin is a completely free media server
(no cloud, no telemetry, no subscription): you store your files, Jellyfin automatically
pulls covers, descriptions and metadata and streams everything to the browser, phone,
smart TV or the Jellyfin app.</p>
<p>The real challenge with a media server is <strong>storage space</strong> – a movie collection quickly
bursts the small system SSD of a VPS. That&rsquo;s why we mount netcup&rsquo;s <strong>Local Block Storage</strong>:
an additional, local disk that appears in the server like a normal drive and on which the
library lives.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Honest about transcoding
  </p>
  <div class="prose-kitchen text-sm">Jellyfin re-encodes videos on demand live into a format your end device understands
(<strong>transcoding</strong>) – that costs <strong>a lot of CPU</strong>. netcup servers are pure CPU machines
<strong>without a graphics card</strong>, so real hardware transcoding (via a GPU) doesn&rsquo;t exist there.
On a root server with <strong>dedicated cores</strong> (RS 1000), software transcoding works well for
one or two parallel streams; but the royal road remains <strong>Direct Play</strong> – more on that
below.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A running <strong>Traefik reverse proxy</strong> with the <code>proxy</code> network and the resolver <code>le</code> – see
<a href="/en/tutorials/traefik-reverse-proxy/">reverse proxy with Traefik</a>.</li>
<li>A subdomain <code>jellyfin.YOUR_DOMAIN</code> with a DNS record to your server IP – see
<a href="/en/tutorials/connect-domain-to-server/">connecting a domain to your server</a>.</li>
<li><strong>Dedicated CPU cores</strong> are clearly at an advantage for smooth streaming (transcoding).
On a shared VPS it works too for Direct Play, but gets tight quickly on re-encoding.</li>
<li>Optional but recommended: booked <strong>netcup Local Block Storage</strong> for the library (step 2).
Without it, you simply put the media in a folder on the system disk – until the space runs
out.</li>
<li>A few <strong>of your own</strong> media files (movies/series/music) that you&rsquo;re allowed to host.</li>
</ul>
<p>Whether your setup really needs dedicated cores is shown by the
<a href="/en/server-calculator/">server calculator</a> – with transcoding the answer is usually yes.</p>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">Root-Server RS 1000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 dedicated cores · 8 GB RAM · 256 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €12.79/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Dedicated cores for transcoding – and Local Block Storage can be added on.</p>
    </div>
    <a href="https://www.netcup.com/en/server/root-server/rs-1000-g12-ip-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € voucher</strong> for new netcup customers:</span>
  <code class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-create-the-dns-record">Step 1: Create the DNS record</h3>
<p>Create <code>jellyfin.YOUR_DOMAIN</code> (A/AAAA to your server IP) and check that it resolves:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short jellyfin.YOUR_DOMAIN</span></span></code></pre></div>
</div>
<p>Your server IP must come back – otherwise Traefik won&rsquo;t fetch a certificate later.</p>
<h3 id="step-2-mount-netcup-local-block-storage">Step 2: Mount netcup Local Block Storage</h3>
<p>The <strong>Local Block Storage</strong> is additional storage you book to your server in the netcup
customer account. Unlike the network-attached <strong>Storage Space</strong> of other providers (which
is connected via NFS and is slower), it appears in the server as a <strong>completely normal
local disk</strong> – ideal for a library because it&rsquo;s fast and freely partitionable.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>No block storage booked?
  </p>
  <div class="prose-kitchen text-sm">Then skip this step and simply create the library under <code>~/jellyfin/media</code> – all the
following commands work the same, only the path is different. You can add the block storage
later at any time and move the data.</div>
</div>
<p>First look at which disks the server knows:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fdisk -l</span></span></code></pre></div>
</div>
<p>The system disk is typically <code>/dev/sda</code> (or <code>/dev/vda</code>). The new block storage appears as
an <strong>additional</strong> device, usually <code>/dev/vdb</code> – recognizable by the size you booked.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Check first, then partition
  </p>
  <div class="prose-kitchen text-sm">The device names can differ per server. If you accidentally partition the <strong>system disk</strong>,
your server is toast. Identify the device unambiguously by its <strong>size</strong> before you
continue. When in doubt, create a <a href="/en/tutorials/netcup-snapshots-scp/">snapshot</a> first.</div>
</div>
<p>Create a partition on the new disk (here <code>/dev/vdb</code>):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo cfdisk /dev/vdb</span></span></code></pre></div>
</div>
<p>Choose <strong>gpt</strong> at the prompt (the modern standard – <strong>dos</strong>/MBR can address at most 2 TiB,
libraries like to grow beyond that), then <strong>New</strong>, take the full size, and write the table
with <strong>Write</strong> (type <code>yes</code>), then <strong>Quit</strong>. The partition <code>/dev/vdb1</code> is created. Format it
with an ext4 filesystem:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkfs.ext4 /dev/vdb1</span></span></code></pre></div>
</div>
<p>Create a mount point and mount the partition:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /mnt/media
</span></span><span class="line"><span class="cl">sudo mount /dev/vdb1 /mnt/media</span></span></code></pre></div>
</div>
<p>So that the disk is mounted automatically <strong>even after a reboot</strong>, you enter it in
<code>/etc/fstab</code> with its <strong>UUID</strong> (not with <code>/dev/vdb1</code>, which can change). Read out the UUID:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo blkid /dev/vdb1</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/dev/vdb1: UUID=&#34;a1b2c3d4-....&#34; TYPE=&#34;ext4&#34; ...</span></span></code></pre></div>
</div>
<p>Open the <code>fstab</code> and append the line with <strong>your</strong> UUID:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/fstab</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">UUID=a1b2c3d4-....   /mnt/media   ext4   defaults   0   2</span></span></code></pre></div>
</div>
<p>Check the entries <strong>before</strong> you rely on the reboot – a typo in the <code>fstab</code> can block the
boot process:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo umount /mnt/media <span class="o">&amp;&amp;</span> sudo mount -a <span class="o">&amp;&amp;</span> df -h /mnt/media</span></span></code></pre></div>
</div>
<p>If no error appears here and <code>df -h</code> shows your new disk under <code>/mnt/media</code>, everything is
entered correctly.</p>
<h3 id="step-3-create-the-project-and-media-folders">Step 3: Create the project and media folders</h3>
<p>Create the Compose project – the configuration is small and may go on the system disk. You
create the subfolders <code>config</code> and <code>cache</code> right along, <strong>before</strong> the container starts for
the first time: if the Docker daemon created them first, they would belong to <code>root</code> – but
Jellyfin runs as UID 1000 (see step 4) and couldn&rsquo;t write into its own <code>/config</code>, the
container would crash on the first start.</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/jellyfin/<span class="o">{</span>config,cache<span class="o">}</span> <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/jellyfin</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Audiobooks belong elsewhere
  </p>
  <div class="prose-kitchen text-sm">Jellyfin can do music – but it is awkward for <strong>audiobooks and podcasts</strong>: it does not sync your
listening position across devices, and it knows nothing about chapter marks or a sleep timer. If you
want both, use <a href="/en/tutorials/audiobookshelf-audiobooks/">Audiobookshelf</a> for those; the two services
run side by side on the same server without trouble.</div>
</div>
<p>And the folder structure for the library on the block storage. Jellyfin sorts best when
movies, series and music are kept separate:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /mnt/media/<span class="o">{</span>filme,serien,musik<span class="o">}</span></span></span></code></pre></div>
</div>
<p>So that the Jellyfin container may read the files, they have to belong to your user.
Determine your user and group ID (usually <code>1000</code>) and hand over the folder:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">id -u <span class="o">&amp;&amp;</span> id -g
</span></span><span class="line"><span class="cl">sudo chown -R 1000:1000 /mnt/media</span></span></code></pre></div>
</div>
<p>Now copy your media into the appropriate subfolders (via <code>scp</code>, <code>rsync</code> or an SFTP client).
For clean recognition, a clear naming helps, e.g. <code>filme/The Godfather (1972)/The Godfather (1972).mkv</code>.</p>
<h3 id="step-4-the-composeyaml">Step 4: The compose.yaml</h3>
<p>Now the central file. Replace <code>jellyfin.YOUR_DOMAIN</code> with your subdomain:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">jellyfin</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">jellyfin/jellyfin:10.11.11</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1000:1000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">JELLYFIN_PublishedServerUrl=https://jellyfin.YOUR_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./cache:/cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/media:/media:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.jellyfin.rule=Host(`jellyfin.YOUR_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.jellyfin.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.jellyfin.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.jellyfin.loadbalancer.server.port=8096&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>What the lines mean:</p>
<ul>
<li><strong><code>image: …:10.11.11</code></strong> – the fixed, current stable version. No <code>latest</code> (see
&ldquo;Maintenance &amp; backups&rdquo;).</li>
<li><strong><code>user: &quot;1000:1000&quot;</code></strong> – Jellyfin runs with <strong>your</strong> user/group ID so it can read the
media. If you adjust other IDs in step 3, change them here too.</li>
<li><strong><code>JELLYFIN_PublishedServerUrl</code></strong> – tells Jellyfin under which public address it&rsquo;s
reachable. This prevents app links from suddenly pointing to an internal IP.</li>
<li><strong><code>volumes</code></strong>: <code>config</code> (settings, users, metadata) and <code>cache</code> live small in the project
folder; the <strong>library</strong> is mounted from the block storage under <code>/mnt/media</code> into the
container as <code>/media</code> – <strong><code>:ro</code></strong> (read-only), so Jellyfin never changes your originals.</li>
<li><strong>The four Traefik labels</strong> are the familiar pattern. New is only the port: Jellyfin
listens on <strong>8096</strong>, hence the <code>loadbalancer.server.port</code> label. Without it, there&rsquo;d be a
<code>502 Bad Gateway</code>.</li>
<li><strong>No <code>ports:</code></strong> – Jellyfin is reachable exclusively via Traefik (HTTPS), not directly from
the internet.</li>
</ul>
<p>Start the stack and watch it boot:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker compose logs -f jellyfin</span></span></code></pre></div>
</div>
<p>On the first start, certificate issuance takes a few seconds. Once <code>Startup complete</code>
appears in the log, Jellyfin is ready.</p>
<h3 id="step-5-the-initial-setup-wizard">Step 5: The initial setup wizard</h3>
<p>Open <code>https://jellyfin.YOUR_DOMAIN</code> in the browser. On the very first call, a wizard guides
you through the basic setup:</p>
<ol>
<li>Choose the <strong>language</strong>.</li>
<li>Create an <strong>administrator account</strong> – set a <strong>long, unique password</strong>, because the login
page is publicly on the net.</li>
<li><strong>Add media libraries</strong>: click &ldquo;Add media library&rdquo;, choose the type (e.g. <em>Movies</em>), and
enter the <strong>container path</strong> as the folder – i.e. <code>/media/filme</code> (not <code>/mnt/media/filme</code>,
that&rsquo;s the path on the host!). Repeat this for <code>serien</code> and <code>musik</code>.</li>
<li>Set the <strong>metadata language</strong> and you can accept the rest.</li>
</ol>
<p><figure class="my-6"><img src="/en/tutorials/jellyfin-media-server/jellyfin-einrichtung_hu_c1c02efb7c260d14.webp" srcset="/en/tutorials/jellyfin-media-server/jellyfin-einrichtung_hu_458de0a2575dec2c.webp 480w, /en/tutorials/jellyfin-media-server/jellyfin-einrichtung_hu_c1c02efb7c260d14.webp 768w, /en/tutorials/jellyfin-media-server/jellyfin-einrichtung_hu_6b6f7e3feb89f399.webp 1200w, /en/tutorials/jellyfin-media-server/jellyfin-einrichtung_hu_27aefc6cf81a8bf0.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/jellyfin-media-server/jellyfin-einrichtung_hu_874c631adaf10bb8.webp"
    alt="The Jellyfin setup wizard on first launch with fields for server name and preferred display language" title="The setup wizard on first launch"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The setup wizard on first launch</figcaption></figure></p>
<p>After the wizard, Jellyfin scans the folders and loads covers and descriptions. For large
collections the first scan takes a while – you see the progress under <strong>Dashboard →
Scheduled Tasks</strong>. After that you land on the home page and see your library with the first
titles:</p>
<p><figure class="my-6"><img src="/en/tutorials/jellyfin-media-server/jellyfin-startseite_hu_a427defcc02cab4a.webp" srcset="/en/tutorials/jellyfin-media-server/jellyfin-startseite_hu_8ee95ee88c46f3a5.webp 480w, /en/tutorials/jellyfin-media-server/jellyfin-startseite_hu_a427defcc02cab4a.webp 768w, /en/tutorials/jellyfin-media-server/jellyfin-startseite_hu_c9cf18598e9354f8.webp 1200w, /en/tutorials/jellyfin-media-server/jellyfin-startseite_hu_36db899c147b3bfc.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/jellyfin-media-server/jellyfin-startseite_hu_f5429822b251a633.webp"
    alt="The Jellyfin home page after setup with the &ldquo;Movies&rdquo; library and a recently added movie" title="The home page with the first library"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The home page with the first library</figcaption></figure></p>
<h3 id="step-6-configure-jellyfin-cleanly-behind-the-proxy">Step 6: Configure Jellyfin cleanly behind the proxy</h3>
<p>Two settings make Jellyfin work correctly behind Traefik. Open <strong>Dashboard → Networking</strong>:</p>
<p><figure class="my-6"><img src="/en/tutorials/jellyfin-media-server/jellyfin-dashboard_hu_49ea4f89f0c94e48.webp" srcset="/en/tutorials/jellyfin-media-server/jellyfin-dashboard_hu_236cd86efabb984.webp 480w, /en/tutorials/jellyfin-media-server/jellyfin-dashboard_hu_49ea4f89f0c94e48.webp 768w, /en/tutorials/jellyfin-media-server/jellyfin-dashboard_hu_d69db5ed4f3767ff.webp 1200w, /en/tutorials/jellyfin-media-server/jellyfin-dashboard_hu_474858f0a34026a.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/jellyfin-media-server/jellyfin-dashboard_hu_656fccc814e03344.webp"
    alt="The Jellyfin administration dashboard with server version 10.11.11, active devices and the storage paths, on the left the menu with network and playback settings" title="The admin dashboard – networking and playback sit here too"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The admin dashboard – networking and playback sit here too</figcaption></figure></p>
<ul>
<li>
<p>Under <strong>Known proxies</strong> you enter the Traefik network so Jellyfin sees the <strong>real</strong> client
IP (instead of Traefik&rsquo;s container IP). You determine the subnet with:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker network inspect proxy --format <span class="s1">&#39;{{ (index .IPAM.Config 0).Subnet }}&#39;</span></span></span></code></pre></div>
</div>
<p>Enter the returned range (e.g. <code>172.19.0.0/16</code>) there.</p>
</li>
<li>
<p><strong>HTTPS</strong>: since Traefik handles the encryption, Jellyfin itself needs <strong>no</strong> TLS. Leave
the HTTPS options in Jellyfin empty – everything runs via the proxy.</p>
</li>
</ul>
<p>WebSockets (for live updates of the interface) Traefik forwards automatically, nothing to do
there.</p>
<h3 id="step-7-transcoding-vs-direct-play">Step 7: Transcoding vs. Direct Play</h3>
<p>Whether your server breaks a sweat is decided here. Two cases:</p>
<ul>
<li><strong>Direct Play</strong> – the end device can play the file as-is. The server just pushes bytes
through, <strong>almost no CPU load</strong>. That&rsquo;s the ideal case.</li>
<li><strong>Transcoding</strong> – format, codec or bitrate don&rsquo;t fit the device (or the line is too slow),
so Jellyfin re-encodes <strong>live</strong>. On a netcup server this happens via the <strong>CPU</strong> – with a
4K stream that can max out a whole server.</li>
</ul>
<p>This is how you keep the load low:</p>
<ul>
<li>Provide <strong>suitable formats</strong>: H.264/AAC in an <code>.mp4</code>/<code>.mkv</code> plays directly on practically
every device. Exotic codecs (e.g. certain 4K HEVC audio tracks) force transcoding.</li>
<li>Set the <strong>client quality</strong> in the app to &ldquo;Original/Direct Play&rdquo; when the line is enough.</li>
<li>Under <strong>Dashboard → Playback</strong> you can set the transcoding settings and a limit for
simultaneous streams.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Hardware transcoding needs a GPU
  </p>
  <div class="prose-kitchen text-sm">The <strong>hardware acceleration</strong> selectable in Jellyfin (VAAPI, QSV, NVENC) requires a graphics
unit under <code>/dev/dri</code>. Standard VPS and root servers at netcup are KVM machines <strong>without</strong> a
passed-through GPU – leave hardware acceleration <strong>off</strong> there, it would only produce errors.
Instead plan with dedicated CPU cores and Direct Play.</div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong><code>502 Bad Gateway</code> on access, even though the container is running.</strong> Traefik reaches the
container but hits the wrong port. Jellyfin listens on <strong>8096</strong> – the label
<code>traefik.http.services.jellyfin.loadbalancer.server.port=8096</code> must be set. Also check that
Jellyfin is on the <strong><code>proxy</code> network</strong>.</p>
<p><strong>The libraries stay empty, even though files are there.</strong> Almost always a <strong>permission
problem</strong>. The container runs as <code>1000:1000</code> (step 4), so the media must belong to that
user: <code>sudo chown -R 1000:1000 /mnt/media</code>. And: in Jellyfin the <strong>container path</strong> must be
entered (<code>/media/filme</code>), not the host path. Afterwards start &ldquo;Scan all libraries&rdquo; under
<strong>Dashboard → Scheduled Tasks</strong>.</p>
<p><strong>Video stutters/buffers, <code>docker stats</code> shows Jellyfin at ~100% CPU.</strong> It&rsquo;s
<strong>transcoding</strong>. Under <strong>Dashboard → Playback</strong> the activity monitor shows whether it says
&ldquo;Transcode&rdquo; instead of &ldquo;Direct Play&rdquo;. Set the client quality to original, change exotic
codecs to a widely supported format, or give the server more (dedicated) cores.</p>
<p><strong>After a reboot the library is gone and Jellyfin shows empty folders.</strong> The block storage
wasn&rsquo;t mounted – usually a missing or wrong <code>fstab</code> entry (step 2). Check with <code>df -h /mnt/media</code> and <code>sudo mount -a</code>. The line must use the <strong>UUID</strong>, not <code>/dev/vdb1</code>.</p>
<p><strong>The login page loads, but the app can&rsquo;t find the server / links point to an internal
address.</strong> <code>JELLYFIN_PublishedServerUrl</code> is missing or wrong. Set it in the <code>compose.yaml</code>
to <code>https://jellyfin.YOUR_DOMAIN</code> and restart with <code>docker compose up -d</code> (a <code>restart</code> isn&rsquo;t
enough for the environment change to take effect).</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>Apply updates deliberately.</strong> The fixed tag (<code>jellyfin:10.11.11</code>) means: you decide when
to update. Bump the tag → <code>docker compose pull</code> → <code>docker compose up -d</code>. Before jumping to
a new major version, read the release notes – database migrations aren&rsquo;t always reversible.</li>
<li><strong>Back up the config, treat the media separately.</strong> The <code>config</code> volume (users, settings,
playback state, metadata) is small and the actually valuable part – back it up
<a href="/en/tutorials/restic-backups/">encrypted off-site with Restic</a>. The <strong>library</strong> itself is
usually too large for a classic backup and reproducible from the original media; decide
deliberately whether you back it up or classify it as &ldquo;replaceable&rdquo;. Honestly: a second
backup of your movie collection costs real storage.</li>
<li><strong>Monitor disk space.</strong> A library grows steadily. Keep an eye on usage
(<code>df -h /mnt/media</code>) and best set up a monitor in
<a href="/en/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> that raises the alarm before the
block storage is full.</li>
<li><strong>Publicly reachable = secure it.</strong> The login page is on the net, and Jellyfin brings <strong>no
two-factor authentication</strong> (not even via an official plugin). So consistently assign
strong passwords, deactivate unused accounts – and for purely private use, consider making
Jellyfin reachable not publicly but only via a VPN.</li>
</ul>
]]></content:encoded></item></channel></rss>