Jellyfin From Scratch: How to Build a Complete Media Server on Linux Mint (Docker, Nginx, and Let’s Encrypt)
Start from a clean OS and end with your own secure, self-hosted streaming platform — complete with HTTPS access, remote streaming, and full media organization.
🧠 Introduction
Jellyfin is a free, open-source media server that lets you organize and stream your movies, shows, and music to any device. Think of it as your personal Netflix — except you control everything.
In this guide, you’ll start from a clean Linux Mint installation and finish with a fully working Jellyfin server running inside Docker, served securely through Nginx with Let’s Encrypt SSL certificates, protected by Fail2ban, and accessible anywhere through a DuckDNS domain.
This tutorial uses CPU-only transcoding (no GPU acceleration) and follows current Docker Compose v2 standards. This means, if want to use your GPU to do all the heave lifting, I ain't gunna show you that here but, you can still use this tutorial to get your server up and running then, configure your GPU later.
🧰 Prerequisites
- 
A clean install of Linux Mint (based on Ubuntu 20.04 or later)
 - 
Sudo privileges
 - Vim - Text editor (or which ever you prefer)
 - 
An active internet connection
 - 
A DuckDNS account and domain (e.g.,
myserver.duckdns.org) - 
Some media files stored locally or on a mounted drive
 
🧱 Step 1 — System Preparation
Start by updating your system and installing a few required packages.
sudo apt update && sudo apt upgrade -y
...give it a minute to do its thang.
sudo apt install -y ca-certificates curl gnupg lsb-release ufw fail2ban
.........This might take another quick minute. If you can any prompts, say "yes" or "y".
Enable the firewall and allow only essential services: (run each line separately)
sudo ufw default deny incoming 
sudo ufw default allow outgoing 
sudo ufw allow OpenSSH 
sudo ufw allow 80,443/tcp 
sudo ufw enable
Check status:
sudo ufw status
You should see SSH, HTTP, and HTTPS allowed. Did it work?
🐳 Step 2 — Install Docker and Docker Compose
Install Docker Engine and the Compose plugin using official repositories: These are to separate programs that work in tandem. "Docker" and "Docker Compose"
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo \ 
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ 
https://download.docker.com/linux/ubuntu \ 
$(lsb_release -cs) stable" | \ 
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable docker
sudo systemctl start docker
Verify installation:
docker --version 
docker compose version
They should both tell you what version of Docker & Docker Compose you have.
📂 Step 3 — Create Directory Structure
Where do you want your Jellyfin to live? For this, we’ll keep Jellyfin and related configs in /opt/jellyfin.
sudo chown -R $USER:$USER /opt/jellyfin
sudo mkdir -p /opt/jellyfin/{config,cache,media,nginx,certbot}
sudo mkdir /mnt/media 
sudo mount /dev/sdX1 /mnt/media 
sudo ln -s /mnt/media /opt/jellyfin/media
Or, you could mount directly to the directory:
sudo mount /dev/sdX1 /opt/jellyfin/media
You need this permanent so, go do that in fstab.
⚙️ Step 4 — Create the Docker Compose File
Create the file:
This is a .yml file. Don't know the language? That's OK. I gotchew. 
sudo vim /opt/jellyfin/docker-compose.yml
Paste this configuration:
above, we made a bunch of directories. config, cache, media, nginx, certbot
Make sure that the directories in here match the ones you made. 
Press "i" to edit the text. Paste this in:
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1001
      - PGID=1001
      - TZ=America/Los_Angeles
    volumes:
      - /home/user/jellyfin/config:/config
      - /home/user/jellyfin/cache:/cache
      - /home/user/jellyfin/media:/media
    ports:
      - 8098:8096
    restart: unless-stopped
Save and exit Esc: wqenter
🌐 Step 5 — Configure Nginx Reverse Proxy
Create a new Nginx configuration file:
mkdir -p /opt/jellyfin/nginx/conf.d
sudo vim /opt/jellyfin/nginx/conf.d/jellyfin.conf
server {
        server_name Your.customesite.com;
            location / {
                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_http_version 1.1;
                proxy_set_header   Upgrade    $http_upgrade;
                proxy_set_header   Connection "upgrade";
                proxy_pass http://localhost:8096; #add your own port number if its not this
        }
    #place holder for certpot later. It will maintain your SSL cert for you.
    # you can delete this line and the one above it once you get the certbot running.
  
}
Your.customesite.com with your actual DuckDNS (or other custom) domain.Esc: wqenter🔑 Step 6 — Get Your SSL Certificate with your domain and Certbot
First, make sure your DuckDNS domain is pointed to your public IP. If you haven't already, go to your domain dashboard and make sure.
Go here to see how to get a an automated SSL cert.
Inside that page, go through the "Certbot : Auto SSL certifications." section. Then come back to continue.
🚀 Step 7 — Start Up Everything
Now bring up all containers:
Be in the directory where your .yml file is located. (step 4)
docker compose up -d
Check containers:
docker ps
Visit your server:
You should see the Jellyfin setup wizard 🎉