{"id":16,"date":"2025-07-29T22:35:53","date_gmt":"2025-07-29T22:35:53","guid":{"rendered":"https:\/\/wallaceat.me\/?p=16"},"modified":"2025-11-24T22:50:24","modified_gmt":"2025-11-24T22:50:24","slug":"setting-up-jellyfin-as-a-self-hosted-media-server","status":"publish","type":"post","link":"https:\/\/wallaceat.me\/?p=16","title":{"rendered":"Setting Up Jellyfin as a Self-Hosted Media Server"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<p>In this post, I share how I set up <strong>Jellyfin<\/strong>, a free and open-source media server, on my home server using <strong>Docker<\/strong>, integrated with <strong>Caddy<\/strong> for HTTPS and a custom subdomain.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf Goals<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Host Jellyfin locally using Docker<\/li>\n\n\n\n<li>Access the media server via <code>https:\/\/tv.wallaceat.me<\/code><\/li>\n\n\n\n<li>Enable HTTPS with automatic certificate management using Caddy<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcc1 Project Structure<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/home\/wallace\/server\/jellyfin\n\u251c\u2500\u2500 docker-compose.yml\n\u251c\u2500\u2500 media\/\n\u251c\u2500\u2500 media2\/\n\u2514\u2500\u2500 fonts\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde9 Docker Compose File<\/h2>\n\n\n\n<p>This is the <code>docker-compose.yml<\/code> I used to spin up Jellyfin:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>version: \"3.8\"\n\nservices:\n  jellyfin:\n    image: jellyfin\/jellyfin\n    container_name: jellyfin\n    user: 1000:1000\n    volumes:\n      - \/home\/wallace\/server\/jellyfin\/config:\/config\n      - \/home\/wallace\/server\/jellyfin\/cache:\/cache\n      - type: bind\n        source: \/server\/jellyfin\/media\n        target: \/media\n      - type: bind\n        source: \/server\/jellyfin\/media2\n        target: \/media2\n      - type: bind\n        source: \/mnt\/torrents\n        target: \/torrent\n        read_only: true\n      - type: bind\n        source: \/path\/to\/fonts\n        target: \/usr\/local\/share\/fonts\/custom\n        read_only: true\n    restart: unless-stopped\n    environment:\n      - JELLYFIN_PublishedServerUrl=https:\/\/tv.wallaceat.me\n    extra_hosts:\n      - 'host.docker.internal:host-gateway'\n    networks:\n      - local\n\nnetworks:\n  local:\n    external: true\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd12 Caddy Reverse Proxy<\/h2>\n\n\n\n<p>I configured the reverse proxy in my existing Caddy container. Here&#8217;s the snippet added to the <code>Caddyfile<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tv.wallaceat.me {\n  reverse_proxy jellyfin:8096\n}<\/code><\/pre>\n\n\n\n<p>Ensure Jellyfin is in the same Docker network that Caddy is part of, or adjust to <code>host.docker.internal:8096<\/code> if needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udf10 Domain and DNS<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A subdomain <code>tv.wallaceat.me<\/code> was created and pointed to the public IP of the server using <strong>Dynu DNS<\/strong><\/li>\n\n\n\n<li><strong>Dynu IP Update Client<\/strong> keeps the IP up to date in case it changes<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u26a0\ufe0f Common Issues I Faced<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Port conflicts<\/strong>: Jellyfin initially used <code>host network<\/code>, which interfered with other services; switching to bridged network fixed it<\/li>\n\n\n\n<li><strong>Reverse proxy errors<\/strong>: Caddy logged connection timeouts when the Jellyfin container was misconfigured or not reachable<\/li>\n\n\n\n<li><strong>Permissions<\/strong>: Mounted volumes needed correct read\/write permissions for Jellyfin to access media files<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udf89 Outcome<\/h2>\n\n\n\n<p>Jellyfin is now fully accessible at <a href=\"https:\/\/tv.wallaceat.me\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/tv.wallaceat.me<\/a> with SSL and remote access. It&#8217;s integrated into my self-hosted stack alongside Immich and WordPress using Caddy as a central reverse proxy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udca1 Lessons Learned<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Using Caddy simplifies HTTPS and domain routing significantly<\/li>\n\n\n\n<li>Proper Docker network configuration is critical for reverse proxying<\/li>\n\n\n\n<li>Persistent volumes are essential to keep configuration and media libraries intact<\/li>\n<\/ul>\n\n\n\n<p>Jellyfin has been a solid and customizable solution for streaming my personal media library across devices, all from a home server setup.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post, I share how I set up Jellyfin, a free and open-source media server, on my home server using Docker, integrated with Caddy for HTTPS and a custom subdomain. \ud83c\udfaf Goals \ud83d\udcc1 Project Structure \ud83e\udde9 Docker Compose File This is the docker-compose.yml I used to spin up Jellyfin: \ud83d\udd12 Caddy Reverse Proxy I [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-16","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts\/16","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=16"}],"version-history":[{"count":2,"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts\/16\/revisions"}],"predecessor-version":[{"id":30,"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts\/16\/revisions\/30"}],"wp:attachment":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=16"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=16"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=16"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}