# 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.

## Questions unlock answers.  
🧠 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
- Install SSH (if you want to connect remotely)
- Vim - Text editor (or which ever you prefer)
- An active internet connection
- A domain name - I am using a free one from DuckDNS.org (e.g., `myserver.duckdns.org`)
- Some media files stored locally or on a mounted drive. I do NOT go over how to mount a drive!
- Router: port forwarding 
    - Ports 22, 80 and 443

##### Doing some of the Prereqs:

- Install your OS. I ain't helping with that part.
- If you even want to have remote access, [Check this one out](https://wiki.danicus.net/books/ssh-secure-shell/page/secure-a-new-computer-ssh) to do some sudo (super user) and some SSH (remote login) stuff.
- <span style="text-decoration: underline;">**Install VIM text editor:**</span>

```bash
sudo apt install vim
```

- <span style="text-decoration: underline;">**You will need a domain name**</span> for this. You can use your own custom one but, if you don't one, get one. duckdns.org is a good free source for that. Ill be using mint1.duckdns.org for this example.
- <span style="text-decoration: underline;">**Where is your media files right now?** </span>If you haven't already, to but them where you are going to be using them at. /media is a good spot or, if you are mounting a drive, /mnt is also a good spot. i'll have a mounted RAID drive/s and will be mounting it in /mnt/jellyfin/media  
    I ain't helppen you with this neither so... if need be, now is a good time to pause and come back once you know where your stuff will be stored.   
    If you need help with a RAID, [*Set up a RAID here*](https://wiki.danicus.net/books/raid-mdadm "RAID | MDADM") - Not hardware RAID. It is a Digital RAID.
- <span style="text-decoration: underline;">**Your Router:**</span>  
     (optional) If you want to have remote access by SSH from other computer, you will need to open port <span style="text-decoration: underline;">22</span> for this. You can use the link above or [go here ](https://wiki.danicus.net/books/ssh-secure-shell/page/secure-a-new-computer-ssh "Secure a new computer | SSH")if you don't know how.  
    **Either way,** you <span style="text-decoration: underline;">will need </span>to open up ports <span style="text-decoration: underline;">80</span> and <span style="text-decoration: underline;">443</span>. This will expose your computer (and network) to the world, through those ports. But, that's OK cause, we want it to for now.

Once you are able to complete those few tasks, we can then continue with getting a Jellyfin server up and running.

---

## 🧱 Step 1 — System Preparation

Start by updating your system and installing a few required packages.

```bash
sudo apt update && sudo apt upgrade -y
```

If this takes less than 10 seconds... read the error and fix it. If not...

...give it a minute to do its thang.

```bash
sudo apt install -y ca-certificates curl gnupg lsb-release ufw fail2ban
```

.........This might take another quick minute. If you have any prompts, say "yes" or "y".

Enable the firewall and allow only essential services: (run each line separately)

```bash
sudo ufw default deny incoming 
sudo ufw default allow outgoing 
sudo ufw allow OpenSSH #or "SSH" or "22"
sudo ufw allow 80/tcp 
sudo ufw allow 443/tcp 
sudo ufw enable
```

Check status:

```bash
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"

```bash
sudo install -m 0755 -d /etc/apt/keyrings
```

```bash
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
```

```bash
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu noble stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

```bash
sudo apt update
```

```bash
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
```

<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk-enable-and-start-doc"><div class="overflow-y-auto p-4" dir="ltr">Enable and start Docker: </div></div>```bash
sudo systemctl enable docker
```

```bash
sudo systemctl start docker
```

Verify installation:

```bash
docker --version 
```

```bash
docker compose version
```

They should both tell you what version of Docker &amp; Docker Compose you have.

#####   


Be in the directory where your .yml file is located. (step 4)

```bash
docker compose up -d
```

  
Check containers:

```bash
docker ps
```

  
Can you see it running?

<span style="text-decoration: underline;">**Visit your server:**</span> Your IP address plus the port number might look something like 192.168.2.196:8000

enter that into your web browser and see if Jellyfin comes up.

---

## 📂 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`.

```bash
sudo chown -R $USER:$USER /opt/jellyfin
```

Use which ever user you need it to be.

```bash
sudo mkdir -p /opt/jellyfin/{config,cache,media,certbot}
```

<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk-where-is-your-media-"><div class="overflow-y-auto p-4" dir="ltr">Where is your media stored at?</div></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--4"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-it-needs-to-be-in-yo">It needs to be in your local drive and ready to use. </div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-if-it%27s-not-being-st">If it's not being stored in your local computer, mount it. </div><div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk-mount-your-media-dri"><div class="overflow-y-auto p-4" dir="ltr">  
</div><div class="overflow-y-auto p-4" dir="ltr">[Mount ](https://wiki.danicus.net/books/samba/page/mount-drives "Mount drives")your media drive to `/opt/jellyfin/media`. Or, if you already have a local address, you can put it there.</div></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--5"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-we-are-not-going-ove">We are not going over how to mount a drive in this tutorial so, if you don't know how to do that part, go learn now, and come back. This example may not make sense to you.</div><div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk-example%3A"><div class="overflow-y-auto p-4" dir="ltr">Example:  
</div></div>```bash
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](https://wiki.danicus.net/books/samba/page/mount-drives "Mount drives").

<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk--6"><div class="overflow-y-auto p-4" dir="ltr">---

</div></div>## ⚙️ 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.

```bash
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:

```yaml
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`  
`:`   
`wq`  
`enter`

- <span style="text-decoration: underline;">Find your PUID:  
    </span>```bash
    tail /etc/passwd
    ```
    
    Use that ID in the below.

- To check your timezone options, enter this:  
    ```bash
    timedatectl list-timezones
    ```
    
    then use the same schema as the above in the yaml file.

---

## 🌐 Step 5 — Configure Nginx Reverse Proxy

### Install Nginx.

```bash
sudo apt install nginx -y
```

<div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-now-locate-the-confi">Now locate the config file so we can make a virtual reverse proxy.</div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-will-probably-be-at-">Will probably be at /etc/nginx/sites-available/</div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--9"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-edit-the-file-%22defau">Edit the file "default" </div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--10"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-paste-the-following%3A">Paste the following:</div>```nginx
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.
  
}

```

<div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-%F0%9F%94%A7-replace-yourdomain">🔧 Replace:</div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-your.customesite.com">- `Your.customesite.com `with your actual DuckDNS (or other custom) domain.
- `localhost:8096` with your current port, if you have changed it.

</div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--11"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--12"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--13"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-save-and-exit%C2%A0esc%3A%C2%A0w-1">Save and exit   
`Esc`  
`:`   
`wq`  
`enter`</div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--14"></div>#### Start/Test it

```bash
sudo systemctl start nginx
```

```bash
sudo systemctl enable nginx
```

<div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--15"><div class="overflow-y-auto p-4" dir="ltr">Go to your domain. </div></div>---

## 🔑 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](https://wiki.danicus.net/books/ssl/page/ssl-certbot-with-lets-encrypt "SSL CertBot with Lets Encrypt") 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.

##### Visit your server:

<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk-https%3A%2F%2Fyourdomain.d"><div class="sticky top-9">  
</div><div class="overflow-y-auto p-4" dir="ltr">`<a href="https://yourdomain.duckdns.org">https:</a><span class="hljs-comment"><a href="https://yourdomain.duckdns.org">//yourdomain.duckdns.org</a> </span>` Or whatever your domain name is. Go see it!</div></div>You should see the Jellyfin setup wizard 🎉

---

<div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-prereq%3A-if-you-%22run%22"></div>